mirror of
https://github.com/tailscale/tailscale.git
synced 2025-12-01 09:32:08 +00:00
tstest/natlab/vnet: add start of IPv6 support
Updates #13038 Change-Id: Ic3d095f167daf6c7129463e881b18f2e0d5693f5 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
31b5239a2f
commit
b78df4d48a
@@ -95,6 +95,10 @@ func findKernelPath(goMod string) (string, error) {
|
||||
|
||||
type addNodeFunc func(c *vnet.Config) *vnet.Node // returns nil to omit test
|
||||
|
||||
func v6cidr(n int) string {
|
||||
return fmt.Sprintf("2000:%d::1/64", n)
|
||||
}
|
||||
|
||||
func easy(c *vnet.Config) *vnet.Node {
|
||||
n := c.NumNodes() + 1
|
||||
return c.AddNode(c.AddNetwork(
|
||||
@@ -102,6 +106,20 @@ func easy(c *vnet.Config) *vnet.Node {
|
||||
fmt.Sprintf("192.168.%d.1/24", n), vnet.EasyNAT))
|
||||
}
|
||||
|
||||
func easyAnd6(c *vnet.Config) *vnet.Node {
|
||||
n := c.NumNodes() + 1
|
||||
return c.AddNode(c.AddNetwork(
|
||||
fmt.Sprintf("2.%d.%d.%d", n, n, n), // public IP
|
||||
fmt.Sprintf("192.168.%d.1/24", n),
|
||||
v6cidr(n),
|
||||
vnet.EasyNAT))
|
||||
}
|
||||
|
||||
func just6(c *vnet.Config) *vnet.Node {
|
||||
n := c.NumNodes() + 1
|
||||
return c.AddNode(c.AddNetwork(v6cidr(n))) // public IPv6 prefix
|
||||
}
|
||||
|
||||
// easy + host firewall
|
||||
func easyFW(c *vnet.Config) *vnet.Node {
|
||||
n := c.NumNodes() + 1
|
||||
@@ -255,6 +273,11 @@ func (nt *natTest) runTest(node1, node2 addNodeFunc) pingRoute {
|
||||
for _, e := range node.Env() {
|
||||
fmt.Fprintf(&envBuf, " tailscaled.env=%s=%s", e.Key, e.Value)
|
||||
}
|
||||
sysLogAddr := net.JoinHostPort(vnet.FakeSyslogIPv4().String(), "995")
|
||||
if node.IsV6Only() {
|
||||
fmt.Fprintf(&envBuf, " tta.nameserver=%s", vnet.FakeDNSIPv6())
|
||||
sysLogAddr = net.JoinHostPort(vnet.FakeSyslogIPv6().String(), "995")
|
||||
}
|
||||
envStr := envBuf.String()
|
||||
|
||||
cmd := exec.Command("qemu-system-x86_64",
|
||||
@@ -262,7 +285,7 @@ func (nt *natTest) runTest(node1, node2 addNodeFunc) pingRoute {
|
||||
"-m", "384M",
|
||||
"-nodefaults", "-no-user-config", "-nographic",
|
||||
"-kernel", nt.kernel,
|
||||
"-append", "console=hvc0 root=PARTUUID=60c24cc1-f3f9-427a-8199-76baa2d60001/PARTNROFF=1 ro init=/gokrazy/init panic=10 oops=panic pci=off nousb tsc=unstable clocksource=hpet gokrazy.remote_syslog.target=52.52.0.9:995 tailscale-tta=1"+envStr,
|
||||
"-append", "console=hvc0 root=PARTUUID=60c24cc1-f3f9-427a-8199-76baa2d60001/PARTNROFF=1 ro init=/gokrazy/init panic=10 oops=panic pci=off nousb tsc=unstable clocksource=hpet gokrazy.remote_syslog.target="+sysLogAddr+" tailscale-tta=1"+envStr,
|
||||
"-drive", "id=blk0,file="+disk+",format=qcow2",
|
||||
"-device", "virtio-blk-device,drive=blk0",
|
||||
"-netdev", "stream,id=net0,addr.type=unix,addr.path="+sockAddr,
|
||||
@@ -445,6 +468,20 @@ func TestEasyEasy(t *testing.T) {
|
||||
nt.want(routeDirect)
|
||||
}
|
||||
|
||||
func TestJustIPv6(t *testing.T) {
|
||||
t.Skip("TODO: get this working")
|
||||
nt := newNatTest(t)
|
||||
nt.runTest(just6, just6)
|
||||
nt.want(routeDirect)
|
||||
}
|
||||
|
||||
func TestEasy4AndJust6(t *testing.T) {
|
||||
t.Skip("TODO: get this working")
|
||||
nt := newNatTest(t)
|
||||
nt.runTest(easyAnd6, just6)
|
||||
nt.want(routeDirect)
|
||||
}
|
||||
|
||||
func TestSameLAN(t *testing.T) {
|
||||
nt := newNatTest(t)
|
||||
nt.runTest(easy, sameLAN)
|
||||
|
||||
@@ -132,6 +132,7 @@ type TailscaledEnv struct {
|
||||
func (c *Config) AddNetwork(opts ...any) *Network {
|
||||
num := len(c.networks)
|
||||
n := &Network{
|
||||
num: num + 1,
|
||||
mac: MAC{0x52, 0xee, 0xee, 0xee, 0xee, byte(num) + 1}, // 52=TS then 0xee for 'etwork
|
||||
}
|
||||
c.networks = append(c.networks, n)
|
||||
@@ -139,9 +140,15 @@ func (c *Config) AddNetwork(opts ...any) *Network {
|
||||
switch o := o.(type) {
|
||||
case string:
|
||||
if ip, err := netip.ParseAddr(o); err == nil {
|
||||
n.wanIP = ip
|
||||
n.wanIP4 = ip
|
||||
} else if ip, err := netip.ParsePrefix(o); err == nil {
|
||||
n.lanIP = ip
|
||||
// If the prefix is IPv4, treat it as the router's internal IPv4 address + CIDR.
|
||||
// If the prefix is IPv6, treat it as the router's WAN IPv6 + CIDR (typically a /64).
|
||||
if ip.Addr().Is4() {
|
||||
n.lanIP4 = ip
|
||||
} else if ip.Addr().Is6() {
|
||||
n.wanIP6 = ip
|
||||
}
|
||||
} else {
|
||||
if n.err == nil {
|
||||
n.err = fmt.Errorf("unknown string option %q", o)
|
||||
@@ -208,6 +215,21 @@ func (n *Node) SetVerboseSyslog(v bool) {
|
||||
n.verboseSyslog = v
|
||||
}
|
||||
|
||||
// IsV6Only reports whether this node is only connected to IPv6 networks.
|
||||
func (n *Node) IsV6Only() bool {
|
||||
for _, net := range n.nets {
|
||||
if net.CanV4() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, net := range n.nets {
|
||||
if net.CanV6() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Network returns the first network this node is connected to,
|
||||
// or nil if none.
|
||||
func (n *Node) Network() *Network {
|
||||
@@ -219,12 +241,15 @@ func (n *Node) Network() *Network {
|
||||
|
||||
// Network is the configuration of a network in the virtual network.
|
||||
type Network struct {
|
||||
num int // 1-based
|
||||
mac MAC // MAC address of the router/gateway
|
||||
natType NAT
|
||||
|
||||
wanIP netip.Addr
|
||||
lanIP netip.Prefix
|
||||
nodes []*Node
|
||||
wanIP6 netip.Prefix // global unicast router in host bits; CIDR is /64 delegated to LAN
|
||||
|
||||
wanIP4 netip.Addr // IPv4 WAN IP, if any
|
||||
lanIP4 netip.Prefix
|
||||
nodes []*Node
|
||||
|
||||
svcs set.Set[NetworkService]
|
||||
|
||||
@@ -232,6 +257,14 @@ type Network struct {
|
||||
err error // carried error
|
||||
}
|
||||
|
||||
func (n *Network) CanV4() bool {
|
||||
return n.lanIP4.IsValid() || n.wanIP4.IsValid()
|
||||
}
|
||||
|
||||
func (n *Network) CanV6() bool {
|
||||
return n.wanIP6.IsValid()
|
||||
}
|
||||
|
||||
func (n *Network) CanTakeMoreNodes() bool {
|
||||
if n.natType == One2OneNAT {
|
||||
return len(n.nodes) == 0
|
||||
@@ -282,24 +315,43 @@ func (s *Server) initFromConfig(c *Config) error {
|
||||
if conf.err != nil {
|
||||
return conf.err
|
||||
}
|
||||
if !conf.lanIP.IsValid() {
|
||||
conf.lanIP = netip.MustParsePrefix("192.168.0.0/24")
|
||||
if !conf.lanIP4.IsValid() && !conf.wanIP6.IsValid() {
|
||||
conf.lanIP4 = netip.MustParsePrefix("192.168.0.0/24")
|
||||
}
|
||||
n := &network{
|
||||
s: s,
|
||||
mac: conf.mac,
|
||||
portmap: conf.svcs.Contains(NATPMP), // TODO: expand network.portmap
|
||||
wanIP: conf.wanIP,
|
||||
lanIP: conf.lanIP,
|
||||
nodesByIP: map[netip.Addr]*node{},
|
||||
logf: logger.WithPrefix(log.Printf, fmt.Sprintf("[net-%v] ", conf.mac)),
|
||||
num: conf.num,
|
||||
s: s,
|
||||
mac: conf.mac,
|
||||
portmap: conf.svcs.Contains(NATPMP), // TODO: expand network.portmap
|
||||
wanIP6: conf.wanIP6,
|
||||
v4: conf.lanIP4.IsValid(),
|
||||
v6: conf.wanIP6.IsValid(),
|
||||
wanIP4: conf.wanIP4,
|
||||
lanIP4: conf.lanIP4,
|
||||
nodesByIP: map[netip.Addr]*node{},
|
||||
nodesByMAC: map[MAC]*node{},
|
||||
logf: logger.WithPrefix(log.Printf, fmt.Sprintf("[net-%v] ", conf.mac)),
|
||||
}
|
||||
netOfConf[conf] = n
|
||||
s.networks.Add(n)
|
||||
if _, ok := s.networkByWAN[conf.wanIP]; ok {
|
||||
return fmt.Errorf("two networks have the same WAN IP %v; Anycast not (yet?) supported", conf.wanIP)
|
||||
if conf.wanIP4.IsValid() {
|
||||
if conf.wanIP4.Is6() {
|
||||
return fmt.Errorf("invalid IPv6 address in wanIP")
|
||||
}
|
||||
if _, ok := s.networkByWAN.Lookup(conf.wanIP4); ok {
|
||||
return fmt.Errorf("two networks have the same WAN IP %v; Anycast not (yet?) supported", conf.wanIP4)
|
||||
}
|
||||
s.networkByWAN.Insert(netip.PrefixFrom(conf.wanIP4, 32), n)
|
||||
}
|
||||
if conf.wanIP6.IsValid() {
|
||||
if conf.wanIP6.Addr().Is4() {
|
||||
return fmt.Errorf("invalid IPv4 address in wanIP6")
|
||||
}
|
||||
if _, ok := s.networkByWAN.LookupPrefix(conf.wanIP6); ok {
|
||||
return fmt.Errorf("two networks have the same WAN IPv6 %v; Anycast not (yet?) supported", conf.wanIP6)
|
||||
}
|
||||
s.networkByWAN.Insert(conf.wanIP6, n)
|
||||
}
|
||||
s.networkByWAN[conf.wanIP] = n
|
||||
n.lanInterfaceID = must.Get(s.pcapWriter.AddInterface(pcapgo.NgInterface{
|
||||
Name: fmt.Sprintf("network%d-lan", i+1),
|
||||
LinkType: layers.LinkTypeIPv4,
|
||||
@@ -330,13 +382,16 @@ func (s *Server) initFromConfig(c *Config) error {
|
||||
s.nodes = append(s.nodes, n)
|
||||
s.nodeByMAC[n.mac] = n
|
||||
|
||||
// Allocate a lanIP for the node. Use the network's CIDR and use final
|
||||
// octet 101 (for first node), 102, etc. The node number comes from the
|
||||
// last octent of the MAC address (0-based)
|
||||
ip4 := n.net.lanIP.Addr().As4()
|
||||
ip4[3] = 100 + n.mac[5]
|
||||
n.lanIP = netip.AddrFrom4(ip4)
|
||||
n.net.nodesByIP[n.lanIP] = n
|
||||
if n.net.v4 {
|
||||
// Allocate a lanIP for the node. Use the network's CIDR and use final
|
||||
// octet 101 (for first node), 102, etc. The node number comes from the
|
||||
// last octent of the MAC address (0-based)
|
||||
ip4 := n.net.lanIP4.Addr().As4()
|
||||
ip4[3] = 100 + n.mac[5]
|
||||
n.lanIP = netip.AddrFrom4(ip4)
|
||||
n.net.nodesByIP[n.lanIP] = n
|
||||
}
|
||||
n.net.nodesByMAC[n.mac] = n
|
||||
}
|
||||
|
||||
// Now that nodes are populated, set up NAT:
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
var vips = map[string]virtualIP{} // DNS name => details
|
||||
|
||||
var (
|
||||
fakeDNS = newVIP("dns", "4.11.4.11", "2000:4:11::4:11")
|
||||
fakeDNS = newVIP("dns", "4.11.4.11", "2411::411")
|
||||
fakeProxyControlplane = newVIP("controlplane.tailscale.com", 1)
|
||||
fakeTestAgent = newVIP("test-driver.tailscale", 2)
|
||||
fakeControl = newVIP("control.tailscale", 3)
|
||||
@@ -31,6 +31,18 @@ func (v virtualIP) Match(a netip.Addr) bool {
|
||||
return v.v4 == a.Unmap() || v.v6 == a
|
||||
}
|
||||
|
||||
// FakeDNSIPv4 returns the fake DNS IPv4 address.
|
||||
func FakeDNSIPv4() netip.Addr { return fakeDNS.v4 }
|
||||
|
||||
// FakeDNSIPv6 returns the fake DNS IPv6 address.
|
||||
func FakeDNSIPv6() netip.Addr { return fakeDNS.v6 }
|
||||
|
||||
// FakeSyslogIPv4 returns the fake syslog IPv4 address.
|
||||
func FakeSyslogIPv4() netip.Addr { return fakeSyslog.v4 }
|
||||
|
||||
// FakeSyslogIPv6 returns the fake syslog IPv6 address.
|
||||
func FakeSyslogIPv6() netip.Addr { return fakeSyslog.v6 }
|
||||
|
||||
// newVIP returns a new virtual IP.
|
||||
//
|
||||
// opts may be an IPv4 an IPv6 (in string form) or an int (bounded by uint8) to
|
||||
@@ -67,8 +79,14 @@ func newVIP(name string, opts ...any) (v virtualIP) {
|
||||
}
|
||||
if !v.v6.IsValid() && v.v4.IsValid() {
|
||||
// Map 1.2.3.4 to 2052::0102:0304
|
||||
a := [16]byte{0: 2, 2: 5, 3: 2} // 2052::
|
||||
copy(a[12:], v.v4.AsSlice())
|
||||
// But make 52.52.0.x map to 2052::x
|
||||
a := [16]byte{0: 0x20, 1: 0x52} // 2052::
|
||||
v4 := v.v4.As4()
|
||||
if v4[0] == 52 && v4[1] == 52 && v4[2] == 0 {
|
||||
a[15] = v4[3]
|
||||
} else {
|
||||
copy(a[12:], v.v4.AsSlice())
|
||||
}
|
||||
v.v6 = netip.AddrFrom16(a)
|
||||
}
|
||||
for _, b := range vips {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user