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>
This commit is contained in:
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.
|
// It should be treated as immutable.
|
||||||
//
|
//
|
||||||
// The nil value is a valid configuration.
|
// The nil value is a valid configuration.
|
||||||
type natV4Config struct {
|
type natConfig struct {
|
||||||
// nativeAddr is the IPv4 Tailscale Address of the current node.
|
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.
|
||||||
|
//
|
||||||
|
// The nil value is a valid configuration.
|
||||||
|
type natFamilyConfig struct {
|
||||||
|
// 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 {
|
||||||
|
hasMasqAddrsForFamily := false ||
|
||||||
|
(addrFam == ipproto.IPProtoVersion4 && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid()) ||
|
||||||
|
(addrFam == ipproto.IPProtoVersion6 && p.V6MasqAddr != nil && p.V6MasqAddr.IsValid())
|
||||||
|
if hasMasqAddrsForFamily {
|
||||||
exitNodeRequiresMasq = true
|
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,11 +608,16 @@ 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
|
||||||
}
|
}
|
||||||
|
test := func(addrFam ipproto.IPProtoVersion) {
|
||||||
var (
|
var (
|
||||||
noIP netip.Addr
|
noIP netip.Addr
|
||||||
|
|
||||||
|
@ -630,6 +635,21 @@ func TestNATCfg(t *testing.T) {
|
||||||
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())}
|
||||||
|
|
||||||
|
peer1IP = netip.MustParseAddr("fd7a:115c:a1e0::b")
|
||||||
|
peer2IP = netip.MustParseAddr("fd7a:115c:a1e0::c")
|
||||||
|
|
||||||
|
subnet = netip.MustParsePrefix("2001:db8::/32")
|
||||||
|
subnetIP = netip.MustParseAddr("2001:db8::FFFF")
|
||||||
|
|
||||||
|
exitRoute = netip.MustParsePrefix("::/0")
|
||||||
|
publicIP = netip.MustParseAddr("2001:4860:4860::8888")
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -779,8 +799,8 @@ func TestNATCfg(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -791,9 +811,15 @@ func TestNATCfg(t *testing.T) {
|
||||||
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
|
||||||
// with the correct parameters when various packet operations are performed.
|
// with the correct parameters when various packet operations are performed.
|
||||||
|
|
|
@ -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,6 +625,7 @@ 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()
|
||||||
|
for _, v6 := range []bool{false, true} {
|
||||||
env := newTestEnv(t)
|
env := newTestEnv(t)
|
||||||
registerNode := func() (*testNode, key.NodePublic) {
|
registerNode := func() (*testNode, key.NodePublic) {
|
||||||
n := newTestNode(t, env)
|
n := newTestNode(t, env)
|
||||||
|
@ -638,11 +639,21 @@ func TestNATPing(t *testing.T) {
|
||||||
n1, k1 := registerNode()
|
n1, k1 := registerNode()
|
||||||
n2, k2 := registerNode()
|
n2, k2 := registerNode()
|
||||||
|
|
||||||
n1IP := n1.AwaitIP()
|
var n1IP, n2IP netip.Addr
|
||||||
n2IP := n2.AwaitIP()
|
if v6 {
|
||||||
|
n1IP = n1.AwaitIP6()
|
||||||
|
n2IP = n2.AwaitIP6()
|
||||||
|
} else {
|
||||||
|
n1IP = n1.AwaitIP4()
|
||||||
|
n2IP = n2.AwaitIP4()
|
||||||
|
}
|
||||||
|
|
||||||
n1ExternalIP := netip.MustParseAddr("100.64.1.1")
|
n1ExternalIP := netip.MustParseAddr("100.64.1.1")
|
||||||
n2ExternalIP := netip.MustParseAddr("100.64.2.1")
|
n2ExternalIP := netip.MustParseAddr("100.64.2.1")
|
||||||
|
if v6 {
|
||||||
|
n1ExternalIP = netip.MustParseAddr("fd7a:115c:a1e0::1a")
|
||||||
|
n2ExternalIP = netip.MustParseAddr("fd7a:115c:a1e0::1b")
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -699,18 +710,23 @@ func TestNATPing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(fmt.Sprintf("v6=%t/%v", v6, tc.name), func(t *testing.T) {
|
||||||
env.Control.SetMasqueradeAddresses(tc.pairs)
|
env.Control.SetMasqueradeAddresses(tc.pairs)
|
||||||
|
|
||||||
|
ipIdx := 0
|
||||||
|
if v6 {
|
||||||
|
ipIdx = 1
|
||||||
|
}
|
||||||
|
|
||||||
s1 := n1.MustStatus()
|
s1 := n1.MustStatus()
|
||||||
n2AsN1Peer := s1.Peer[k2]
|
n2AsN1Peer := s1.Peer[k2]
|
||||||
if got := n2AsN1Peer.TailscaleIPs[0]; got != tc.n1SeesN2IP {
|
if got := n2AsN1Peer.TailscaleIPs[ipIdx]; got != tc.n1SeesN2IP {
|
||||||
t.Fatalf("n1 sees n2 as %v; want %v", got, tc.n1SeesN2IP)
|
t.Fatalf("n1 sees n2 as %v; want %v", got, tc.n1SeesN2IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
s2 := n2.MustStatus()
|
s2 := n2.MustStatus()
|
||||||
n1AsN2Peer := s2.Peer[k1]
|
n1AsN2Peer := s2.Peer[k1]
|
||||||
if got := n1AsN2Peer.TailscaleIPs[0]; got != tc.n2SeesN1IP {
|
if got := n1AsN2Peer.TailscaleIPs[ipIdx]; got != tc.n2SeesN1IP {
|
||||||
t.Fatalf("n2 sees n1 as %v; want %v", got, tc.n2SeesN1IP)
|
t.Fatalf("n2 sees n1 as %v; want %v", got, tc.n2SeesN1IP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -732,6 +748,7 @@ func TestNATPing(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLogoutRemovesAllPeers(t *testing.T) {
|
func TestLogoutRemovesAllPeers(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -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,9 +900,14 @@ 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() {
|
||||||
|
if peerAddress.Is6() {
|
||||||
|
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.Addresses[0] = netip.PrefixFrom(peerAddress, peerAddress.BitLen())
|
||||||
p.AllowedIPs[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…
Reference in New Issue