|
|
|
@@ -0,0 +1,257 @@
|
|
|
|
|
// Copyright (c) Tailscale Inc & contributors
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package vmtest_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
|
"tailscale.com/tstest/natlab/vmtest"
|
|
|
|
|
"tailscale.com/tstest/natlab/vnet"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var knownBroken = flag.Bool("known-broken", false, "run known-broken tests")
|
|
|
|
|
|
|
|
|
|
func v6cidr(n int) string {
|
|
|
|
|
return fmt.Sprintf("2000:%d::1/64", n)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func easy(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT),
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func easyAnd6(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n),
|
|
|
|
|
v6cidr(n),
|
|
|
|
|
vnet.EasyNAT),
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// easyNoControlDiscoRotate sets up a node with easy NAT, cuts traffic to
|
|
|
|
|
// control after connecting, and then rotates the disco key to simulate a newly
|
|
|
|
|
// started node (from a disco perspective).
|
|
|
|
|
func easyNoControlDiscoRotate(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
nw := env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n),
|
|
|
|
|
vnet.EasyNAT)
|
|
|
|
|
nw.SetPostConnectControlBlackhole(true)
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_USE_CACHED_NETMAP", Value: "true"},
|
|
|
|
|
vnet.RotateDisco, vnet.PreICMPPing,
|
|
|
|
|
nw,
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// easyFW is easy + host firewall.
|
|
|
|
|
func easyFW(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
vnet.HostFirewall,
|
|
|
|
|
env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT),
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// easyPMPFWPlusBPF is easy + port mapping + host firewall + BPF.
|
|
|
|
|
func easyPMPFWPlusBPF(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
vnet.HostFirewall,
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_ENABLE_RAW_DISCO", Value: "true"},
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_DEBUG_RAW_DISCO", Value: "1"},
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_DEBUG_DISCO", Value: "1"},
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_LOG_VERBOSITY", Value: "2"},
|
|
|
|
|
env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT, vnet.NATPMP),
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// easyPMPFWNoBPF is easy + port mapping + host firewall - BPF.
|
|
|
|
|
func easyPMPFWNoBPF(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
vnet.HostFirewall,
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_ENABLE_RAW_DISCO", Value: "false"},
|
|
|
|
|
env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT, vnet.NATPMP),
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func hard(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("10.0.%d.1/24", n), vnet.HardNAT),
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func hardNoDERPOrEndpoints(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("10.0.%d.1/24", n), vnet.HardNAT),
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_DEBUG_STRIP_ENDPOINTS", Value: "1"},
|
|
|
|
|
vnet.TailscaledEnv{Key: "TS_DEBUG_STRIP_HOME_DERP", Value: "1"},
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func just6(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n),
|
|
|
|
|
env.AddNetwork(v6cidr(n)), // public IPv6 prefix
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func v6AndBlackholedIPv4(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
nw := env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n),
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n),
|
|
|
|
|
v6cidr(n),
|
|
|
|
|
vnet.EasyNAT)
|
|
|
|
|
nw.SetBlackholedIPv4(true)
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n), nw, vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEasyEasy(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDirect, easy, easy)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestTwoEasyNoControlDiscoRotate tests a situation where two nodes have been
|
|
|
|
|
// online and connected through control, but then lose control access and also
|
|
|
|
|
// rotate keys. It is not a perfect proxy for a cached node, as the node will
|
|
|
|
|
// still have a mapState and not use the backup method of inserting keys into
|
|
|
|
|
// the engine directly.
|
|
|
|
|
func TestTwoEasyNoControlDiscoRotate(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDirect, easyNoControlDiscoRotate, easyNoControlDiscoRotate)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestJustIPv6(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDirect, just6, just6)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestEasy4AndJust6(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDirect, easyAnd6, just6)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSameLAN(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
var sharedNW *vnet.Network
|
|
|
|
|
makeEasy := func(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
sharedNW = env.AddNetwork(
|
|
|
|
|
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
|
|
|
|
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT)
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n), sharedNW, vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
sameLAN := func(env *vmtest.Env) *vmtest.Node {
|
|
|
|
|
n := env.NumNodes()
|
|
|
|
|
return env.AddNode(fmt.Sprintf("node-%d", n), sharedNW, vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
}
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteLocal, makeEasy, sameLAN)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestBPFDisco tests https://github.com/tailscale/tailscale/issues/3824 ...
|
|
|
|
|
// * server behind a Hard NAT
|
|
|
|
|
// * client behind a NAT with UPnP support
|
|
|
|
|
// * client machine has a stateful host firewall (e.g. ufw)
|
|
|
|
|
func TestBPFDisco(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDirect, easyPMPFWPlusBPF, hard)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHostFWNoBPF(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDERP, easyPMPFWNoBPF, hard)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestHostFWPair(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDirect, easyFW, easyFW)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestOneHostFW(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDirect, easy, easyFW)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Issue tailscale/corp#26438: use learned DERP route as send path of last
|
|
|
|
|
// resort
|
|
|
|
|
//
|
|
|
|
|
// See (*magicsock.Conn).fallbackDERPRegionForPeer and its comment for
|
|
|
|
|
// background.
|
|
|
|
|
//
|
|
|
|
|
// This sets up a test with two nodes that must use DERP to communicate but the
|
|
|
|
|
// target of the ping (the second node) additionally is not getting DERP or
|
|
|
|
|
// Endpoint updates from the control plane. (Or rather, it's getting them but is
|
|
|
|
|
// configured to scrub them right when they come off the network before being
|
|
|
|
|
// processed) This then tests whether node2, upon receiving a packet, will be
|
|
|
|
|
// able to reply to node1 since it knows neither node1's endpoints nor its home
|
|
|
|
|
// DERP. The only reply route it can use is that fact that it just received a
|
|
|
|
|
// packet over a particular DERP from that peer.
|
|
|
|
|
func TestFallbackDERPRegionForPeer(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
env.RunConnectivityTest(t.Name(), vmtest.PingRouteDERP, hard, hardNoDERPOrEndpoints)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestSingleJustIPv6 tests that a node can connect to control with just IPv6.
|
|
|
|
|
// Since there is no connectivity testing needed, the test just asserts the
|
|
|
|
|
// node coming up which will be asserted by env.Start().
|
|
|
|
|
func TestSingleJustIPv6(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
just6(env)
|
|
|
|
|
env.Start()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestSingleDualBrokenIPv4 tests a dual-stack node with broken
|
|
|
|
|
// (blackholed) IPv4.
|
|
|
|
|
//
|
|
|
|
|
// See https://github.com/tailscale/tailscale/issues/13346
|
|
|
|
|
func TestSingleDualBrokenIPv4(t *testing.T) {
|
|
|
|
|
if !*knownBroken {
|
|
|
|
|
t.Skip("skipping known-broken test; set --known-broken to run; see https://github.com/tailscale/tailscale/issues/13346")
|
|
|
|
|
}
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
v6AndBlackholedIPv4(env)
|
|
|
|
|
env.Start()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestNonTailscaleCGNATEndpoint(t *testing.T) {
|
|
|
|
|
env := vmtest.New(t)
|
|
|
|
|
|
|
|
|
|
cgnatNW := env.AddNetwork("100.65.1.1/16", "2.1.1.1", vnet.EasyNAT)
|
|
|
|
|
n0 := env.AddNode("node-0",
|
|
|
|
|
cgnatNW,
|
|
|
|
|
vmtest.DontJoinTailnet(),
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
n1 := env.AddNode("node-1",
|
|
|
|
|
cgnatNW,
|
|
|
|
|
tailcfg.NodeCapMap{tailcfg.NodeAttrDisableLinuxCGNATDropRule: nil},
|
|
|
|
|
vmtest.OS(vmtest.Gokrazy))
|
|
|
|
|
|
|
|
|
|
env.Start()
|
|
|
|
|
env.LANPing(n1, n0.LanIP(cgnatNW))
|
|
|
|
|
}
|