net/tstun: finish wiring IPv6 NAT support

Updates https://github.com/tailscale/corp/issues/11202
Updates ENG-991
Signed-off-by: Tom DNetto <tom@tailscale.com>
pull/9315/merge
Tom DNetto 8 months ago committed by Tom
parent 52e4f24c58
commit da1b917575

@ -98,8 +98,8 @@ type Wrapper struct {
// timeNow, if non-nil, will be used to obtain the current time. // timeNow, if non-nil, will be used to obtain the current time.
timeNow func() time.Time timeNow func() time.Time
// natV4Config stores the current IPv4 NAT configuration. // natConfig stores the current NAT configuration.
natV4Config atomic.Pointer[natV4Config] natConfig atomic.Pointer[natConfig]
// vectorBuffer stores the oldest unconsumed packet vector from tdev. It is // vectorBuffer stores the oldest unconsumed packet vector from tdev. It is
// allocated in wrap() and the underlying arrays should never grow. // allocated in wrap() and the underlying arrays should never grow.
@ -481,14 +481,9 @@ func (t *Wrapper) sendVectorOutbound(r tunVectorReadResult) {
t.vectorOutbound <- r t.vectorOutbound <- r
} }
// snatV4 does SNAT on p if it's an IPv4 packet and the destination // snat does SNAT on p if the destination address requires a different source address.
// address requires a different source address. func (t *Wrapper) snat(p *packet.Parsed) {
func (t *Wrapper) snatV4(p *packet.Parsed) { nc := t.natConfig.Load()
if p.IPVersion != 4 {
return
}
nc := t.natV4Config.Load()
oldSrc := p.Src.Addr() oldSrc := p.Src.Addr()
newSrc := nc.selectSrcIP(oldSrc, p.Dst.Addr()) newSrc := nc.selectSrcIP(oldSrc, p.Dst.Addr())
if oldSrc != newSrc { if oldSrc != newSrc {
@ -496,13 +491,9 @@ func (t *Wrapper) snatV4(p *packet.Parsed) {
} }
} }
// dnatV4 does destination NAT on p if it's an IPv4 packet. // dnat does destination NAT on p.
func (t *Wrapper) dnatV4(p *packet.Parsed) { func (t *Wrapper) dnat(p *packet.Parsed) {
if p.IPVersion != 4 { nc := t.natConfig.Load()
return
}
nc := t.natV4Config.Load()
oldDst := p.Dst.Addr() oldDst := p.Dst.Addr()
newDst := nc.mapDstIP(oldDst) newDst := nc.mapDstIP(oldDst)
if newDst != oldDst { if newDst != oldDst {
@ -521,15 +512,79 @@ func findV4(addrs []netip.Prefix) netip.Addr {
return netip.Addr{} return netip.Addr{}
} }
// natV4Config is the configuration for IPv4 NAT. // findV6 returns the first Tailscale IPv6 address in addrs.
func findV6(addrs []netip.Prefix) netip.Addr {
for _, ap := range addrs {
a := ap.Addr()
if a.Is6() && tsaddr.IsTailscaleIP(a) {
return a
}
}
return netip.Addr{}
}
// natConfig is the configuration for NAT.
// It should be treated as immutable.
//
// The nil value is a valid configuration.
type natConfig struct {
v4, v6 *natFamilyConfig
}
func (c *natConfig) String() string {
if c == nil {
return "<nil>"
}
var b strings.Builder
b.WriteString("natConfig{")
fmt.Fprintf(&b, "v4: %v, ", c.v4)
fmt.Fprintf(&b, "v6: %v", c.v6)
b.WriteString("}")
return b.String()
}
// mapDstIP returns the destination IP to use for a packet to dst.
// If dst is not one of the listen addresses, it is returned as-is,
// otherwise the native address is returned.
func (c *natConfig) mapDstIP(oldDst netip.Addr) netip.Addr {
if c == nil {
return oldDst
}
if oldDst.Is4() {
return c.v4.mapDstIP(oldDst)
}
if oldDst.Is6() {
return c.v6.mapDstIP(oldDst)
}
return oldDst
}
// selectSrcIP returns the source IP to use for a packet to dst.
// If the packet is not from the native address, it is returned as-is.
func (c *natConfig) selectSrcIP(oldSrc, dst netip.Addr) netip.Addr {
if c == nil {
return oldSrc
}
if oldSrc.Is4() {
return c.v4.selectSrcIP(oldSrc, dst)
}
if oldSrc.Is6() {
return c.v6.selectSrcIP(oldSrc, dst)
}
return oldSrc
}
// natFamilyConfig is the NAT configuration for a particular
// address family.
// It should be treated as immutable. // It should be treated as immutable.
// //
// The nil value is a valid configuration. // The nil value is a valid configuration.
type natV4Config struct { type natFamilyConfig struct {
// nativeAddr is the IPv4 Tailscale Address of the current node. // nativeAddr is the Tailscale Address of the current node.
nativeAddr netip.Addr nativeAddr netip.Addr
// listenAddrs is the set of IPv4 addresses that should be // listenAddrs is the set of addresses that should be
// mapped to the native address. These are the addresses that // mapped to the native address. These are the addresses that
// peers will use to connect to this node. // peers will use to connect to this node.
listenAddrs views.Map[netip.Addr, struct{}] // masqAddr -> struct{} listenAddrs views.Map[netip.Addr, struct{}] // masqAddr -> struct{}
@ -545,12 +600,12 @@ type natV4Config struct {
dstAddrToPeerKeyMapper *table.RoutingTable dstAddrToPeerKeyMapper *table.RoutingTable
} }
func (c *natV4Config) String() string { func (c *natFamilyConfig) String() string {
if c == nil { if c == nil {
return "<nil>" return "natFamilyConfig(nil)"
} }
var b strings.Builder var b strings.Builder
b.WriteString("natV4Config{") b.WriteString("natFamilyConfig{")
fmt.Fprintf(&b, "nativeAddr: %v, ", c.nativeAddr) fmt.Fprintf(&b, "nativeAddr: %v, ", c.nativeAddr)
fmt.Fprint(&b, "listenAddrs: [") fmt.Fprint(&b, "listenAddrs: [")
@ -586,7 +641,7 @@ func (c *natV4Config) String() string {
// mapDstIP returns the destination IP to use for a packet to dst. // mapDstIP returns the destination IP to use for a packet to dst.
// If dst is not one of the listen addresses, it is returned as-is, // If dst is not one of the listen addresses, it is returned as-is,
// otherwise the native address is returned. // otherwise the native address is returned.
func (c *natV4Config) mapDstIP(oldDst netip.Addr) netip.Addr { func (c *natFamilyConfig) mapDstIP(oldDst netip.Addr) netip.Addr {
if c == nil { if c == nil {
return oldDst return oldDst
} }
@ -598,7 +653,7 @@ func (c *natV4Config) mapDstIP(oldDst netip.Addr) netip.Addr {
// selectSrcIP returns the source IP to use for a packet to dst. // selectSrcIP returns the source IP to use for a packet to dst.
// If the packet is not from the native address, it is returned as-is. // If the packet is not from the native address, it is returned as-is.
func (c *natV4Config) selectSrcIP(oldSrc, dst netip.Addr) netip.Addr { func (c *natFamilyConfig) selectSrcIP(oldSrc, dst netip.Addr) netip.Addr {
if c == nil { if c == nil {
return oldSrc return oldSrc
} }
@ -615,16 +670,25 @@ func (c *natV4Config) selectSrcIP(oldSrc, dst netip.Addr) netip.Addr {
return oldSrc return oldSrc
} }
// natV4ConfigFromWGConfig generates a natV4Config from nm. // natConfigFromWGConfig generates a natFamilyConfig from nm,
// If v4 NAT is not required, it returns nil. // for the indicated address family.
func natV4ConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config { // If NAT is not required for that address family, it returns nil.
func natConfigFromWGConfig(wcfg *wgcfg.Config, addrFam ipproto.IPProtoVersion) *natFamilyConfig {
if wcfg == nil { if wcfg == nil {
return nil return nil
} }
nativeAddr := findV4(wcfg.Addresses)
var nativeAddr netip.Addr
switch addrFam {
case ipproto.IPProtoVersion4:
nativeAddr = findV4(wcfg.Addresses)
case ipproto.IPProtoVersion6:
nativeAddr = findV6(wcfg.Addresses)
}
if !nativeAddr.IsValid() { if !nativeAddr.IsValid() {
return nil return nil
} }
var ( var (
rt table.RoutingTableBuilder rt table.RoutingTableBuilder
dstMasqAddrs map[key.NodePublic]netip.Addr dstMasqAddrs map[key.NodePublic]netip.Addr
@ -637,17 +701,25 @@ func natV4ConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading
for _, p := range wcfg.Peers { for _, p := range wcfg.Peers {
isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6()) isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6())
if isExitNode && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() { if isExitNode {
exitNodeRequiresMasq = true hasMasqAddrsForFamily := false ||
(addrFam == ipproto.IPProtoVersion4 && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid()) ||
(addrFam == ipproto.IPProtoVersion6 && p.V6MasqAddr != nil && p.V6MasqAddr.IsValid())
if hasMasqAddrsForFamily {
exitNodeRequiresMasq = true
}
break break
} }
} }
for i := range wcfg.Peers { for i := range wcfg.Peers {
p := &wcfg.Peers[i] p := &wcfg.Peers[i]
var addrToUse netip.Addr var addrToUse netip.Addr
if p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() { if addrFam == ipproto.IPProtoVersion4 && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
addrToUse = *p.V4MasqAddr addrToUse = *p.V4MasqAddr
mak.Set(&listenAddrs, addrToUse, struct{}{}) mak.Set(&listenAddrs, addrToUse, struct{}{})
} else if addrFam == ipproto.IPProtoVersion6 && p.V6MasqAddr != nil && p.V6MasqAddr.IsValid() {
addrToUse = *p.V6MasqAddr
mak.Set(&listenAddrs, addrToUse, struct{}{})
} else if exitNodeRequiresMasq { } else if exitNodeRequiresMasq {
addrToUse = nativeAddr addrToUse = nativeAddr
} else { } else {
@ -659,7 +731,7 @@ func natV4ConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
if len(listenAddrs) == 0 && len(dstMasqAddrs) == 0 { if len(listenAddrs) == 0 && len(dstMasqAddrs) == 0 {
return nil return nil
} }
return &natV4Config{ return &natFamilyConfig{
nativeAddr: nativeAddr, nativeAddr: nativeAddr,
listenAddrs: views.MapOf(listenAddrs), listenAddrs: views.MapOf(listenAddrs),
dstMasqAddrs: views.MapOf(dstMasqAddrs), dstMasqAddrs: views.MapOf(dstMasqAddrs),
@ -668,10 +740,14 @@ func natV4ConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
} }
// SetNetMap is called when a new NetworkMap is received. // SetNetMap is called when a new NetworkMap is received.
// It currently (2023-03-01) only updates the IPv4 NAT configuration.
func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) { func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) {
cfg := natV4ConfigFromWGConfig(wcfg) v4, v6 := natConfigFromWGConfig(wcfg, ipproto.IPProtoVersion4), natConfigFromWGConfig(wcfg, ipproto.IPProtoVersion6)
old := t.natV4Config.Swap(cfg) var cfg *natConfig
if v4 != nil || v6 != nil {
cfg = &natConfig{v4: v4, v6: v6}
}
old := t.natConfig.Swap(cfg)
if !reflect.DeepEqual(old, cfg) { if !reflect.DeepEqual(old, cfg) {
t.logf("nat config: %v", cfg) t.logf("nat config: %v", cfg)
} }
@ -786,7 +862,7 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
for _, data := range res.data { for _, data := range res.data {
p.Decode(data[res.dataOffset:]) p.Decode(data[res.dataOffset:])
t.snatV4(p) t.snat(p)
if m := t.destIPActivity.Load(); m != nil { if m := t.destIPActivity.Load(); m != nil {
if fn := m[p.Dst.Addr()]; fn != nil { if fn := m[p.Dst.Addr()]; fn != nil {
fn() fn()
@ -843,7 +919,7 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, buf []byte, offset int) (int
p := parsedPacketPool.Get().(*packet.Parsed) p := parsedPacketPool.Get().(*packet.Parsed)
defer parsedPacketPool.Put(p) defer parsedPacketPool.Put(p)
p.Decode(buf[offset : offset+n]) p.Decode(buf[offset : offset+n])
t.snatV4(p) t.snat(p)
if m := t.destIPActivity.Load(); m != nil { if m := t.destIPActivity.Load(); m != nil {
if fn := m[p.Dst.Addr()]; fn != nil { if fn := m[p.Dst.Addr()]; fn != nil {
@ -965,7 +1041,7 @@ func (t *Wrapper) Write(buffs [][]byte, offset int) (int, error) {
captHook := t.captureHook.Load() captHook := t.captureHook.Load()
for _, buff := range buffs { for _, buff := range buffs {
p.Decode(buff[offset:]) p.Decode(buff[offset:])
t.dnatV4(p) t.dnat(p)
if !t.disableFilter { if !t.disableFilter {
if t.filterPacketInboundFromWireGuard(p, captHook) != filter.Accept { if t.filterPacketInboundFromWireGuard(p, captHook) != filter.Accept {
metricPacketInDrop.Add(1) metricPacketInDrop.Add(1)
@ -1030,7 +1106,7 @@ func (t *Wrapper) InjectInboundPacketBuffer(pkt stack.PacketBufferPtr) error {
if captHook != nil { if captHook != nil {
captHook(capture.SynthesizedToLocal, t.now(), p.Buffer(), p.CaptureMeta) captHook(capture.SynthesizedToLocal, t.now(), p.Buffer(), p.CaptureMeta)
} }
t.dnatV4(p) t.dnat(p)
return t.InjectInboundDirect(buf, PacketStartOffset) return t.InjectInboundDirect(buf, PacketStartOffset)
} }

@ -608,191 +608,217 @@ func TestNATCfg(t *testing.T) {
AllowedIPs: []netip.Prefix{ AllowedIPs: []netip.Prefix{
netip.PrefixFrom(ip, ip.BitLen()), netip.PrefixFrom(ip, ip.BitLen()),
}, },
V4MasqAddr: ptr.To(masqIP), }
if masqIP.Is4() {
p.V4MasqAddr = ptr.To(masqIP)
} else {
p.V6MasqAddr = ptr.To(masqIP)
} }
p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...) p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...)
return p return p
} }
var ( test := func(addrFam ipproto.IPProtoVersion) {
noIP netip.Addr var (
noIP netip.Addr
selfNativeIP = netip.MustParseAddr("100.64.0.1") selfNativeIP = netip.MustParseAddr("100.64.0.1")
selfEIP1 = netip.MustParseAddr("100.64.1.1") selfEIP1 = netip.MustParseAddr("100.64.1.1")
selfEIP2 = netip.MustParseAddr("100.64.1.2") selfEIP2 = netip.MustParseAddr("100.64.1.2")
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())} selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
peer1IP = netip.MustParseAddr("100.64.0.2") peer1IP = netip.MustParseAddr("100.64.0.2")
peer2IP = netip.MustParseAddr("100.64.0.3") peer2IP = netip.MustParseAddr("100.64.0.3")
subnet = netip.MustParsePrefix("192.168.0.0/24") subnet = netip.MustParsePrefix("192.168.0.0/24")
subnetIP = netip.MustParseAddr("192.168.0.1") subnetIP = netip.MustParseAddr("192.168.0.1")
exitRoute = netip.MustParsePrefix("0.0.0.0/0") exitRoute = netip.MustParsePrefix("0.0.0.0/0")
publicIP = netip.MustParseAddr("8.8.8.8") publicIP = netip.MustParseAddr("8.8.8.8")
) )
if addrFam == ipproto.IPProtoVersion6 {
selfNativeIP = netip.MustParseAddr("fd7a:115c:a1e0::a")
selfEIP1 = netip.MustParseAddr("fd7a:115c:a1e0::1a")
selfEIP2 = netip.MustParseAddr("fd7a:115c:a1e0::1b")
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
tests := []struct { peer1IP = netip.MustParseAddr("fd7a:115c:a1e0::b")
name string peer2IP = netip.MustParseAddr("fd7a:115c:a1e0::c")
wcfg *wgcfg.Config
snatMap map[netip.Addr]netip.Addr // dst -> src subnet = netip.MustParsePrefix("2001:db8::/32")
dnatMap map[netip.Addr]netip.Addr subnetIP = netip.MustParseAddr("2001:db8::FFFF")
}{
{ exitRoute = netip.MustParsePrefix("::/0")
name: "no-cfg", publicIP = netip.MustParseAddr("2001:4860:4860::8888")
wcfg: nil, }
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfNativeIP, tests := []struct {
peer2IP: selfNativeIP, name string
subnetIP: selfNativeIP, wcfg *wgcfg.Config
}, snatMap map[netip.Addr]netip.Addr // dst -> src
dnatMap: map[netip.Addr]netip.Addr{ dnatMap map[netip.Addr]netip.Addr
selfNativeIP: selfNativeIP, }{
selfEIP1: selfEIP1, {
selfEIP2: selfEIP2, name: "no-cfg",
}, wcfg: nil,
}, snatMap: map[netip.Addr]netip.Addr{
{ peer1IP: selfNativeIP,
name: "single-peer-requires-nat", peer2IP: selfNativeIP,
wcfg: &wgcfg.Config{ subnetIP: selfNativeIP,
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, noIP),
node(peer2IP, selfEIP1),
}, },
}, dnatMap: map[netip.Addr]netip.Addr{
snatMap: map[netip.Addr]netip.Addr{ selfNativeIP: selfNativeIP,
peer1IP: selfNativeIP, selfEIP1: selfEIP1,
peer2IP: selfEIP1, selfEIP2: selfEIP2,
subnetIP: selfNativeIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfEIP2,
subnetIP: subnetIP,
},
},
{
name: "multiple-peers-require-nat",
wcfg: &wgcfg.Config{
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, selfEIP1),
node(peer2IP, selfEIP2),
}, },
}, },
snatMap: map[netip.Addr]netip.Addr{ {
peer1IP: selfEIP1, name: "single-peer-requires-nat",
peer2IP: selfEIP2, wcfg: &wgcfg.Config{
subnetIP: selfNativeIP, Addresses: selfAddrs,
}, Peers: []wgcfg.Peer{
dnatMap: map[netip.Addr]netip.Addr{ node(peer1IP, noIP),
selfNativeIP: selfNativeIP, node(peer2IP, selfEIP1),
selfEIP1: selfNativeIP, },
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
},
},
{
name: "multiple-peers-require-nat-with-subnet",
wcfg: &wgcfg.Config{
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, selfEIP1),
node(peer2IP, selfEIP2, subnet),
}, },
}, snatMap: map[netip.Addr]netip.Addr{
snatMap: map[netip.Addr]netip.Addr{ peer1IP: selfNativeIP,
peer1IP: selfEIP1, peer2IP: selfEIP1,
peer2IP: selfEIP2, subnetIP: selfNativeIP,
subnetIP: selfEIP2,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
},
},
{
name: "multiple-peers-require-nat-with-default-route",
wcfg: &wgcfg.Config{
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, selfEIP1),
node(peer2IP, selfEIP2, exitRoute),
}, },
}, dnatMap: map[netip.Addr]netip.Addr{
snatMap: map[netip.Addr]netip.Addr{ selfNativeIP: selfNativeIP,
peer1IP: selfEIP1, selfEIP1: selfNativeIP,
peer2IP: selfEIP2, selfEIP2: selfEIP2,
publicIP: selfEIP2, subnetIP: subnetIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
},
},
{
name: "no-nat",
wcfg: &wgcfg.Config{
Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, noIP),
node(peer2IP, noIP),
}, },
}, },
snatMap: map[netip.Addr]netip.Addr{ {
peer1IP: selfNativeIP, name: "multiple-peers-require-nat",
peer2IP: selfNativeIP, wcfg: &wgcfg.Config{
subnetIP: selfNativeIP, Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, selfEIP1),
node(peer2IP, selfEIP2),
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfEIP1,
peer2IP: selfEIP2,
subnetIP: selfNativeIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
},
}, },
dnatMap: map[netip.Addr]netip.Addr{ {
selfNativeIP: selfNativeIP, name: "multiple-peers-require-nat-with-subnet",
selfEIP1: selfEIP1, wcfg: &wgcfg.Config{
selfEIP2: selfEIP2, Addresses: selfAddrs,
subnetIP: subnetIP, Peers: []wgcfg.Peer{
node(peer1IP, selfEIP1),
node(peer2IP, selfEIP2, subnet),
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfEIP1,
peer2IP: selfEIP2,
subnetIP: selfEIP2,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
},
}, },
}, {
{ name: "multiple-peers-require-nat-with-default-route",
name: "exit-node-require-nat-peer-doesnt", wcfg: &wgcfg.Config{
wcfg: &wgcfg.Config{ Addresses: selfAddrs,
Addresses: selfAddrs, Peers: []wgcfg.Peer{
Peers: []wgcfg.Peer{ node(peer1IP, selfEIP1),
node(peer1IP, noIP), node(peer2IP, selfEIP2, exitRoute),
node(peer2IP, selfEIP2, exitRoute), },
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfEIP1,
peer2IP: selfEIP2,
publicIP: selfEIP2,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfNativeIP,
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
}, },
}, },
snatMap: map[netip.Addr]netip.Addr{ {
peer1IP: selfNativeIP, name: "no-nat",
peer2IP: selfEIP2, wcfg: &wgcfg.Config{
publicIP: selfEIP2, Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, noIP),
node(peer2IP, noIP),
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfNativeIP,
peer2IP: selfNativeIP,
subnetIP: selfNativeIP,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP1: selfEIP1,
selfEIP2: selfEIP2,
subnetIP: subnetIP,
},
}, },
dnatMap: map[netip.Addr]netip.Addr{ {
selfNativeIP: selfNativeIP, name: "exit-node-require-nat-peer-doesnt",
selfEIP2: selfNativeIP, wcfg: &wgcfg.Config{
subnetIP: subnetIP, Addresses: selfAddrs,
Peers: []wgcfg.Peer{
node(peer1IP, noIP),
node(peer2IP, selfEIP2, exitRoute),
},
},
snatMap: map[netip.Addr]netip.Addr{
peer1IP: selfNativeIP,
peer2IP: selfEIP2,
publicIP: selfEIP2,
},
dnatMap: map[netip.Addr]netip.Addr{
selfNativeIP: selfNativeIP,
selfEIP2: selfNativeIP,
subnetIP: subnetIP,
},
}, },
}, }
}
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(fmt.Sprintf("%v/%v", addrFam, tc.name), func(t *testing.T) {
ncfg := natV4ConfigFromWGConfig(tc.wcfg) ncfg := natConfigFromWGConfig(tc.wcfg, addrFam)
for peer, want := range tc.snatMap { for peer, want := range tc.snatMap {
if got := ncfg.selectSrcIP(selfNativeIP, peer); got != want { if got := ncfg.selectSrcIP(selfNativeIP, peer); got != want {
t.Errorf("selectSrcIP[%v]: got %v; want %v", peer, got, want) t.Errorf("selectSrcIP[%v]: got %v; want %v", peer, got, want)
}
} }
} for dstIP, want := range tc.dnatMap {
for dstIP, want := range tc.dnatMap { if got := ncfg.mapDstIP(dstIP); got != want {
if got := ncfg.mapDstIP(dstIP); got != want { t.Errorf("mapDstIP[%v]: got %v; want %v", dstIP, got, want)
t.Errorf("mapDstIP[%v]: got %v; want %v", dstIP, got, want) }
} }
} if t.Failed() {
}) t.Logf("%v", ncfg)
}
})
}
} }
test(ipproto.IPProtoVersion4)
test(ipproto.IPProtoVersion6)
} }
// TestCaptureHook verifies that the Wrapper.captureHook callback is called // TestCaptureHook verifies that the Wrapper.captureHook callback is called

@ -117,7 +117,8 @@ type CapabilityVersion int
// - 74: 2023-09-18: Client understands NodeCapMap // - 74: 2023-09-18: Client understands NodeCapMap
// - 75: 2023-09-12: Client understands NodeAttrDNSForwarderDisableTCPRetries // - 75: 2023-09-12: Client understands NodeAttrDNSForwarderDisableTCPRetries
// - 76: 2023-09-20: Client understands ExitNodeDNSResolvers for IsWireGuardOnly nodes // - 76: 2023-09-20: Client understands ExitNodeDNSResolvers for IsWireGuardOnly nodes
const CurrentCapabilityVersion CapabilityVersion = 76 // - 77: 2023-10-03: Client understands Peers[].SelfNodeV6MasqAddrForThisPeer
const CurrentCapabilityVersion CapabilityVersion = 77
type StableID string type StableID string

@ -76,7 +76,7 @@ func TestOneNodeUpNoAuth(t *testing.T) {
n1.AwaitResponding() n1.AwaitResponding()
n1.MustUp() n1.MustUp()
t.Logf("Got IP: %v", n1.AwaitIP()) t.Logf("Got IP: %v", n1.AwaitIP4())
n1.AwaitRunning() n1.AwaitRunning()
d1.MustCleanShutdown(t) d1.MustCleanShutdown(t)
@ -130,7 +130,7 @@ func TestControlKnobs(t *testing.T) {
n1.AwaitResponding() n1.AwaitResponding()
n1.MustUp() n1.MustUp()
t.Logf("Got IP: %v", n1.AwaitIP()) t.Logf("Got IP: %v", n1.AwaitIP4())
n1.AwaitRunning() n1.AwaitRunning()
cmd := n1.Tailscale("debug", "control-knobs") cmd := n1.Tailscale("debug", "control-knobs")
@ -212,7 +212,7 @@ func TestStateSavedOnStart(t *testing.T) {
n1.AwaitResponding() n1.AwaitResponding()
n1.MustUp() n1.MustUp()
t.Logf("Got IP: %v", n1.AwaitIP()) t.Logf("Got IP: %v", n1.AwaitIP4())
n1.AwaitRunning() n1.AwaitRunning()
p1 := n1.diskPrefs() p1 := n1.diskPrefs()
@ -271,7 +271,7 @@ func TestOneNodeUpAuth(t *testing.T) {
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
t.Fatalf("up: %v", err) t.Fatalf("up: %v", err)
} }
t.Logf("Got IP: %v", n1.AwaitIP()) t.Logf("Got IP: %v", n1.AwaitIP4())
n1.AwaitRunning() n1.AwaitRunning()
@ -574,7 +574,7 @@ func TestNoControlConnWhenDown(t *testing.T) {
// Come up the first time. // Come up the first time.
n1.MustUp() n1.MustUp()
ip1 := n1.AwaitIP() ip1 := n1.AwaitIP4()
n1.AwaitRunning() n1.AwaitRunning()
// Then bring it down and stop the daemon. // Then bring it down and stop the daemon.
@ -590,7 +590,7 @@ func TestNoControlConnWhenDown(t *testing.T) {
t.Fatalf("after restart, state = %q; want %q", got, want) t.Fatalf("after restart, state = %q; want %q", got, want)
} }
ip2 := n1.AwaitIP() ip2 := n1.AwaitIP4()
if ip1 != ip2 { if ip1 != ip2 {
t.Errorf("IPs different: %q vs %q", ip1, ip2) t.Errorf("IPs different: %q vs %q", ip1, ip2)
} }
@ -615,7 +615,7 @@ func TestOneNodeUpWindowsStyle(t *testing.T) {
n1.AwaitResponding() n1.AwaitResponding()
n1.MustUp("--unattended") n1.MustUp("--unattended")
t.Logf("Got IP: %v", n1.AwaitIP()) t.Logf("Got IP: %v", n1.AwaitIP4())
n1.AwaitRunning() n1.AwaitRunning()
d1.MustCleanShutdown(t) d1.MustCleanShutdown(t)
@ -625,111 +625,128 @@ func TestOneNodeUpWindowsStyle(t *testing.T) {
// tries to do bi-directional pings between them. // tries to do bi-directional pings between them.
func TestNATPing(t *testing.T) { func TestNATPing(t *testing.T) {
t.Parallel() t.Parallel()
env := newTestEnv(t) for _, v6 := range []bool{false, true} {
registerNode := func() (*testNode, key.NodePublic) { env := newTestEnv(t)
n := newTestNode(t, env) registerNode := func() (*testNode, key.NodePublic) {
n.StartDaemon() n := newTestNode(t, env)
n.AwaitListening() n.StartDaemon()
n.MustUp() n.AwaitListening()
n.AwaitRunning() n.MustUp()
k := n.MustStatus().Self.PublicKey n.AwaitRunning()
return n, k k := n.MustStatus().Self.PublicKey
} return n, k
n1, k1 := registerNode() }
n2, k2 := registerNode() n1, k1 := registerNode()
n2, k2 := registerNode()
n1IP := n1.AwaitIP()
n2IP := n2.AwaitIP() var n1IP, n2IP netip.Addr
if v6 {
n1ExternalIP := netip.MustParseAddr("100.64.1.1") n1IP = n1.AwaitIP6()
n2ExternalIP := netip.MustParseAddr("100.64.2.1") n2IP = n2.AwaitIP6()
} else {
tests := []struct { n1IP = n1.AwaitIP4()
name string n2IP = n2.AwaitIP4()
pairs []testcontrol.MasqueradePair }
n1SeesN2IP netip.Addr
n2SeesN1IP netip.Addr n1ExternalIP := netip.MustParseAddr("100.64.1.1")
}{ n2ExternalIP := netip.MustParseAddr("100.64.2.1")
{ if v6 {
name: "no_nat", n1ExternalIP = netip.MustParseAddr("fd7a:115c:a1e0::1a")
n1SeesN2IP: n2IP, n2ExternalIP = netip.MustParseAddr("fd7a:115c:a1e0::1b")
n2SeesN1IP: n1IP, }
},
{ tests := []struct {
name: "n1_has_external_ip", name string
pairs: []testcontrol.MasqueradePair{ pairs []testcontrol.MasqueradePair
{ n1SeesN2IP netip.Addr
Node: k1, n2SeesN1IP netip.Addr
Peer: k2, }{
NodeMasqueradesAs: n1ExternalIP, {
}, name: "no_nat",
n1SeesN2IP: n2IP,
n2SeesN1IP: n1IP,
}, },
n1SeesN2IP: n2IP, {
n2SeesN1IP: n1ExternalIP, name: "n1_has_external_ip",
}, pairs: []testcontrol.MasqueradePair{
{ {
name: "n2_has_external_ip", Node: k1,
pairs: []testcontrol.MasqueradePair{ Peer: k2,
{ NodeMasqueradesAs: n1ExternalIP,
Node: k2, },
Peer: k1,
NodeMasqueradesAs: n2ExternalIP,
}, },
n1SeesN2IP: n2IP,
n2SeesN1IP: n1ExternalIP,
}, },
n1SeesN2IP: n2ExternalIP, {
n2SeesN1IP: n1IP, name: "n2_has_external_ip",
}, pairs: []testcontrol.MasqueradePair{
{ {
name: "both_have_external_ips", Node: k2,
pairs: []testcontrol.MasqueradePair{ Peer: k1,
{ NodeMasqueradesAs: n2ExternalIP,
Node: k1, },
Peer: k2,
NodeMasqueradesAs: n1ExternalIP,
}, },
{ n1SeesN2IP: n2ExternalIP,
Node: k2, n2SeesN1IP: n1IP,
Peer: k1, },
NodeMasqueradesAs: n2ExternalIP, {
name: "both_have_external_ips",
pairs: []testcontrol.MasqueradePair{
{
Node: k1,
Peer: k2,
NodeMasqueradesAs: n1ExternalIP,
},
{
Node: k2,
Peer: k1,
NodeMasqueradesAs: n2ExternalIP,
},
}, },
n1SeesN2IP: n2ExternalIP,
n2SeesN1IP: n1ExternalIP,
}, },
n1SeesN2IP: n2ExternalIP, }
n2SeesN1IP: n1ExternalIP,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
env.Control.SetMasqueradeAddresses(tc.pairs)
s1 := n1.MustStatus()
n2AsN1Peer := s1.Peer[k2]
if got := n2AsN1Peer.TailscaleIPs[0]; got != tc.n1SeesN2IP {
t.Fatalf("n1 sees n2 as %v; want %v", got, tc.n1SeesN2IP)
}
s2 := n2.MustStatus()
n1AsN2Peer := s2.Peer[k1]
if got := n1AsN2Peer.TailscaleIPs[0]; got != tc.n2SeesN1IP {
t.Fatalf("n2 sees n1 as %v; want %v", got, tc.n2SeesN1IP)
}
if err := n1.Tailscale("ping", tc.n1SeesN2IP.String()).Run(); err != nil {
t.Fatal(err)
}
if err := n1.Tailscale("ping", "-peerapi", tc.n1SeesN2IP.String()).Run(); err != nil {
t.Fatal(err)
}
if err := n2.Tailscale("ping", tc.n2SeesN1IP.String()).Run(); err != nil {
t.Fatal(err)
}
if err := n2.Tailscale("ping", "-peerapi", tc.n2SeesN1IP.String()).Run(); err != nil { for _, tc := range tests {
t.Fatal(err) t.Run(fmt.Sprintf("v6=%t/%v", v6, tc.name), func(t *testing.T) {
} env.Control.SetMasqueradeAddresses(tc.pairs)
})
ipIdx := 0
if v6 {
ipIdx = 1
}
s1 := n1.MustStatus()
n2AsN1Peer := s1.Peer[k2]
if got := n2AsN1Peer.TailscaleIPs[ipIdx]; got != tc.n1SeesN2IP {
t.Fatalf("n1 sees n2 as %v; want %v", got, tc.n1SeesN2IP)
}
s2 := n2.MustStatus()
n1AsN2Peer := s2.Peer[k1]
if got := n1AsN2Peer.TailscaleIPs[ipIdx]; got != tc.n2SeesN1IP {
t.Fatalf("n2 sees n1 as %v; want %v", got, tc.n2SeesN1IP)
}
if err := n1.Tailscale("ping", tc.n1SeesN2IP.String()).Run(); err != nil {
t.Fatal(err)
}
if err := n1.Tailscale("ping", "-peerapi", tc.n1SeesN2IP.String()).Run(); err != nil {
t.Fatal(err)
}
if err := n2.Tailscale("ping", tc.n2SeesN1IP.String()).Run(); err != nil {
t.Fatal(err)
}
if err := n2.Tailscale("ping", "-peerapi", tc.n2SeesN1IP.String()).Run(); err != nil {
t.Fatal(err)
}
})
}
} }
} }
@ -743,7 +760,7 @@ func TestLogoutRemovesAllPeers(t *testing.T) {
nodes[i].StartDaemon() nodes[i].StartDaemon()
nodes[i].AwaitResponding() nodes[i].AwaitResponding()
nodes[i].MustUp() nodes[i].MustUp()
nodes[i].AwaitIP() nodes[i].AwaitIP4()
nodes[i].AwaitRunning() nodes[i].AwaitRunning()
} }
expectedPeers := len(nodes) - 1 expectedPeers := len(nodes) - 1
@ -758,7 +775,7 @@ func TestLogoutRemovesAllPeers(t *testing.T) {
if err := tstest.WaitFor(20*time.Second, func() error { if err := tstest.WaitFor(20*time.Second, func() error {
return nodes[i].Ping(nodes[j]) return nodes[i].Ping(nodes[j])
}); err != nil { }); err != nil {
t.Fatalf("ping %v -> %v: %v", nodes[i].AwaitIP(), nodes[j].AwaitIP(), err) t.Fatalf("ping %v -> %v: %v", nodes[i].AwaitIP4(), nodes[j].AwaitIP4(), err)
} }
} }
} }
@ -783,7 +800,7 @@ func TestLogoutRemovesAllPeers(t *testing.T) {
nodes[0].MustUp() // This will create a new node nodes[0].MustUp() // This will create a new node
expectedPeers++ expectedPeers++
nodes[0].AwaitIP() nodes[0].AwaitIP4()
wantNode0PeerCount(expectedPeers) // all existing peers and the new node wantNode0PeerCount(expectedPeers) // all existing peers and the new node
} }
@ -1107,8 +1124,8 @@ func (n *testNode) MustLogOut() {
func (n *testNode) Ping(otherNode *testNode) error { func (n *testNode) Ping(otherNode *testNode) error {
t := n.env.t t := n.env.t
ip := otherNode.AwaitIP().String() ip := otherNode.AwaitIP4().String()
t.Logf("Running ping %v (from %v)...", ip, n.AwaitIP()) t.Logf("Running ping %v (from %v)...", ip, n.AwaitIP4())
return n.Tailscale("ping", ip).Run() return n.Tailscale("ping", ip).Run()
} }
@ -1162,14 +1179,22 @@ func (n *testNode) AwaitIPs() []netip.Addr {
return addrs return addrs
} }
// AwaitIP returns the IP address of n. // AwaitIP4 returns the IPv4 address of n.
func (n *testNode) AwaitIP() netip.Addr { func (n *testNode) AwaitIP4() netip.Addr {
t := n.env.t t := n.env.t
t.Helper() t.Helper()
ips := n.AwaitIPs() ips := n.AwaitIPs()
return ips[0] return ips[0]
} }
// AwaitIP6 returns the IPv6 address of n.
func (n *testNode) AwaitIP6() netip.Addr {
t := n.env.t
t.Helper()
ips := n.AwaitIPs()
return ips[1]
}
// AwaitRunning waits for n to reach the IPN state "Running". // AwaitRunning waits for n to reach the IPN state "Running".
func (n *testNode) AwaitRunning() { func (n *testNode) AwaitRunning() {
t := n.env.t t := n.env.t

@ -900,8 +900,13 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
peerAddress := s.masquerades[p.Key][node.Key] peerAddress := s.masquerades[p.Key][node.Key]
s.mu.Unlock() s.mu.Unlock()
if peerAddress.IsValid() { if peerAddress.IsValid() {
p.Addresses[0] = netip.PrefixFrom(peerAddress, peerAddress.BitLen()) if peerAddress.Is6() {
p.AllowedIPs[0] = netip.PrefixFrom(peerAddress, peerAddress.BitLen()) p.Addresses[1] = netip.PrefixFrom(peerAddress, peerAddress.BitLen())
p.AllowedIPs[1] = netip.PrefixFrom(peerAddress, peerAddress.BitLen())
} else {
p.Addresses[0] = netip.PrefixFrom(peerAddress, peerAddress.BitLen())
p.AllowedIPs[0] = netip.PrefixFrom(peerAddress, peerAddress.BitLen())
}
} }
res.Peers = append(res.Peers, p) res.Peers = append(res.Peers, p)
} }

@ -6,6 +6,26 @@ package ipproto
import "fmt" import "fmt"
// IPProtoVersion describes the IP address version.
type IPProtoVersion uint8
// Valid IPProtoVersion values.
const (
IPProtoVersion4 = 4
IPProtoVersion6 = 6
)
func (p IPProtoVersion) String() string {
switch p {
case IPProtoVersion4:
return "IPv4"
case IPProtoVersion6:
return "IPv6"
default:
return fmt.Sprintf("IPProtoVersion-%d", int(p))
}
}
// Proto is an IP subprotocol as defined by the IANA protocol // Proto is an IP subprotocol as defined by the IANA protocol
// numbers list // numbers list
// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml), // (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),

Loading…
Cancel
Save