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:
Brad Fitzpatrick
2024-08-13 21:09:53 -07:00
committed by Brad Fitzpatrick
parent 31b5239a2f
commit b78df4d48a
7 changed files with 660 additions and 199 deletions

View File

@@ -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: