diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index ee2f31986..764648d9b 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -256,6 +256,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/net/tsdial from tailscale.com/control/controlclient+ 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+ + tailscale.com/net/tstun/table from tailscale.com/net/tstun tailscale.com/net/wsconn from tailscale.com/control/controlhttp+ tailscale.com/paths from tailscale.com/ipn/ipnlocal+ 💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal @@ -264,6 +265,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LD 💣 tailscale.com/ssh/tailssh from tailscale.com/cmd/tailscaled tailscale.com/syncs from tailscale.com/net/netcheck+ tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+ + 💣 tailscale.com/tempfork/device from tailscale.com/net/tstun/table LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh tailscale.com/tka from tailscale.com/ipn/ipnlocal+ W tailscale.com/tsconst from tailscale.com/net/interfaces diff --git a/net/tstun/wrap.go b/net/tstun/wrap.go index 3a58bebde..94d1950fb 100644 --- a/net/tstun/wrap.go +++ b/net/tstun/wrap.go @@ -25,17 +25,18 @@ import ( "tailscale.com/net/connstats" "tailscale.com/net/packet" "tailscale.com/net/tsaddr" + "tailscale.com/net/tstun/table" "tailscale.com/syncs" "tailscale.com/tstime/mono" "tailscale.com/types/ipproto" "tailscale.com/types/key" "tailscale.com/types/logger" - "tailscale.com/types/netmap" "tailscale.com/types/views" "tailscale.com/util/clientmetric" "tailscale.com/util/mak" "tailscale.com/wgengine/capture" "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/wgcfg" ) const maxBufferSize = device.MaxMessageSize @@ -522,10 +523,12 @@ type natV4Config struct { // dstMasqAddrs is map of dst addresses to their respective MasqueradeAsIP // addresses. The MasqueradeAsIP address is the address that should be used // as the source address for packets to dst. - dstMasqAddrs views.Map[netip.Addr, netip.Addr] // dst -> masqAddr + dstMasqAddrs views.Map[key.NodePublic, netip.Addr] // dst -> masqAddr - // TODO(maisem/nyghtowl): add support for subnets and exit nodes and test them. - // Determine IP routing table algorithm to use - e.g. ART? + // dstAddrToPeerKeyMapper is the routing table used to map a given dst IP to + // the peer key responsible for that IP. + // It only contains peers that require a MasqueradeAsIP address. + dstAddrToPeerKeyMapper *table.RoutingTable } // mapDstIP returns the destination IP to use for a packet to dst. @@ -550,51 +553,55 @@ func (c *natV4Config) selectSrcIP(oldSrc, dst netip.Addr) netip.Addr { if oldSrc != c.nativeAddr { return oldSrc } - if eip, ok := c.dstMasqAddrs.GetOk(dst); ok { + p, ok := c.dstAddrToPeerKeyMapper.Lookup(dst) + if !ok { + return oldSrc + } + if eip, ok := c.dstMasqAddrs.GetOk(p); ok { return eip } return oldSrc } -// natConfigFromNetMap generates a natV4Config from nm. +// natConfigFromWireGuardConfig generates a natV4Config from nm. // If v4 NAT is not required, it returns nil. -func natConfigFromNetMap(nm *netmap.NetworkMap) *natV4Config { - if nm == nil || nm.SelfNode == nil { +func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config { + if wcfg == nil { return nil } - nativeAddr := findV4(nm.SelfNode.Addresses) + nativeAddr := findV4(wcfg.Addresses) if !nativeAddr.IsValid() { return nil } var ( - dstMasqAddrs map[netip.Addr]netip.Addr + rt table.RoutingTableBuilder + dstMasqAddrs map[key.NodePublic]netip.Addr listenAddrs map[netip.Addr]struct{} ) - for _, p := range nm.Peers { - if !p.SelfNodeV4MasqAddrForThisPeer.IsValid() { + for i := range wcfg.Peers { + p := &wcfg.Peers[i] + if !p.V4MasqAddr.IsValid() { continue } - peerV4 := findV4(p.Addresses) - if !peerV4.IsValid() { - continue - } - mak.Set(&dstMasqAddrs, peerV4, p.SelfNodeV4MasqAddrForThisPeer) - mak.Set(&listenAddrs, p.SelfNodeV4MasqAddrForThisPeer, struct{}{}) + rt.InsertOrReplace(p.PublicKey, p.AllowedIPs...) + mak.Set(&dstMasqAddrs, p.PublicKey, p.V4MasqAddr) + mak.Set(&listenAddrs, p.V4MasqAddr, struct{}{}) } if len(listenAddrs) == 0 || len(dstMasqAddrs) == 0 { return nil } return &natV4Config{ - nativeAddr: nativeAddr, - listenAddrs: views.MapOf(listenAddrs), - dstMasqAddrs: views.MapOf(dstMasqAddrs), + nativeAddr: nativeAddr, + listenAddrs: views.MapOf(listenAddrs), + dstMasqAddrs: views.MapOf(dstMasqAddrs), + dstAddrToPeerKeyMapper: rt.Build(), } } // SetNetMap is called when a new NetworkMap is received. // It currently (2023-03-01) only updates the IPv4 NAT configuration. -func (t *Wrapper) SetNetMap(nm *netmap.NetworkMap) { - cfg := natConfigFromNetMap(nm) +func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) { + cfg := natConfigFromWGConfig(wcfg) old := t.natV4Config.Swap(cfg) if !reflect.DeepEqual(old, cfg) { t.logf("nat config: %+v", cfg) diff --git a/net/tstun/wrap_test.go b/net/tstun/wrap_test.go index a9cc2998c..683183e7d 100644 --- a/net/tstun/wrap_test.go +++ b/net/tstun/wrap_test.go @@ -25,16 +25,15 @@ import ( "tailscale.com/net/connstats" "tailscale.com/net/netaddr" "tailscale.com/net/packet" - "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/tstime/mono" "tailscale.com/types/ipproto" "tailscale.com/types/key" "tailscale.com/types/logger" "tailscale.com/types/netlogtype" - "tailscale.com/types/netmap" "tailscale.com/util/must" "tailscale.com/wgengine/filter" + "tailscale.com/wgengine/wgcfg" ) func udp4(src, dst string, sport, dport uint16) []byte { @@ -597,13 +596,16 @@ func TestFilterDiscoLoop(t *testing.T) { } func TestNATCfg(t *testing.T) { - node := func(ip, eip netip.Addr) *tailcfg.Node { - return &tailcfg.Node{ - Addresses: []netip.Prefix{ + node := func(ip, eip netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer { + p := wgcfg.Peer{ + PublicKey: key.NewNode().Public(), + AllowedIPs: []netip.Prefix{ netip.PrefixFrom(ip, ip.BitLen()), }, - SelfNodeV4MasqAddrForThisPeer: eip, + V4MasqAddr: eip, } + p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...) + return p } var ( noIP netip.Addr @@ -615,20 +617,20 @@ func TestNATCfg(t *testing.T) { peer1IP = netip.MustParseAddr("100.64.0.2") peer2IP = netip.MustParseAddr("100.64.0.3") - // subnets should not be impacted. - // TODO(maisem/nyghtowl): add support for subnets and exit nodes and test them. subnet = netip.MustParseAddr("192.168.0.1") + + selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())} ) tests := []struct { name string - nm *netmap.NetworkMap + wcfg *wgcfg.Config snatMap map[netip.Addr]netip.Addr // dst -> src dnatMap map[netip.Addr]netip.Addr }{ { - name: "no-netmap", - nm: nil, + name: "no-cfg", + wcfg: nil, snatMap: map[netip.Addr]netip.Addr{ peer1IP: selfNativeIP, peer2IP: selfNativeIP, @@ -642,9 +644,9 @@ func TestNATCfg(t *testing.T) { }, { name: "single-peer-requires-nat", - nm: &netmap.NetworkMap{ - SelfNode: node(selfNativeIP, noIP), - Peers: []*tailcfg.Node{ + wcfg: &wgcfg.Config{ + Addresses: selfAddrs, + Peers: []wgcfg.Peer{ node(peer1IP, noIP), node(peer2IP, selfEIP1), }, @@ -663,9 +665,9 @@ func TestNATCfg(t *testing.T) { }, { name: "multiple-peers-require-nat", - nm: &netmap.NetworkMap{ - SelfNode: node(selfNativeIP, noIP), - Peers: []*tailcfg.Node{ + wcfg: &wgcfg.Config{ + Addresses: selfAddrs, + Peers: []wgcfg.Peer{ node(peer1IP, selfEIP1), node(peer2IP, selfEIP2), }, @@ -682,11 +684,53 @@ func TestNATCfg(t *testing.T) { subnet: subnet, }, }, + { + name: "multiple-peers-require-nat-with-subnet", + wcfg: &wgcfg.Config{ + Addresses: selfAddrs, + Peers: []wgcfg.Peer{ + node(peer1IP, selfEIP1), + node(peer2IP, selfEIP2, netip.MustParsePrefix("192.168.0.0/24")), + }, + }, + snatMap: map[netip.Addr]netip.Addr{ + peer1IP: selfEIP1, + peer2IP: selfEIP2, + subnet: selfEIP2, + }, + dnatMap: map[netip.Addr]netip.Addr{ + selfNativeIP: selfNativeIP, + selfEIP1: selfNativeIP, + selfEIP2: selfNativeIP, + subnet: subnet, + }, + }, + { + name: "multiple-peers-require-nat-with-default-route", + wcfg: &wgcfg.Config{ + Addresses: selfAddrs, + Peers: []wgcfg.Peer{ + node(peer1IP, selfEIP1), + node(peer2IP, selfEIP2, netip.MustParsePrefix("0.0.0.0/0")), + }, + }, + snatMap: map[netip.Addr]netip.Addr{ + peer1IP: selfEIP1, + peer2IP: selfEIP2, + netip.MustParseAddr("8.8.8.8"): selfEIP2, + }, + dnatMap: map[netip.Addr]netip.Addr{ + selfNativeIP: selfNativeIP, + selfEIP1: selfNativeIP, + selfEIP2: selfNativeIP, + subnet: subnet, + }, + }, { name: "no-nat", - nm: &netmap.NetworkMap{ - SelfNode: node(selfNativeIP, noIP), - Peers: []*tailcfg.Node{ + wcfg: &wgcfg.Config{ + Addresses: selfAddrs, + Peers: []wgcfg.Peer{ node(peer1IP, noIP), node(peer2IP, noIP), }, @@ -707,15 +751,15 @@ func TestNATCfg(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - ncfg := natConfigFromNetMap(tc.nm) + ncfg := natConfigFromWGConfig(tc.wcfg) for peer, want := range tc.snatMap { if got := ncfg.selectSrcIP(selfNativeIP, peer); got != want { - t.Errorf("selectSrcIP: got %v; want %v", got, want) + t.Errorf("selectSrcIP[%v]: got %v; want %v", peer, got, want) } } for dstIP, want := range tc.dnatMap { if got := ncfg.mapDstIP(dstIP); got != want { - t.Errorf("mapDstIP: got %v; want %v", got, want) + t.Errorf("mapDstIP[%v]: got %v; want %v", dstIP, got, want) } } }) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 9f23c7050..8b3bc2146 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -807,6 +807,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config, e.wgLock.Lock() defer e.wgLock.Unlock() + e.tundev.SetWGConfig(cfg) e.lastDNSConfig = dnsCfg peerSet := make(map[key.NodePublic]struct{}, len(cfg.Peers)) @@ -1205,7 +1206,6 @@ func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) { e.magicConn.SetNetworkMap(nm) e.mu.Lock() e.netMap = nm - e.tundev.SetNetMap(nm) callbacks := make([]NetworkMapCallback, 0, 4) for _, fn := range e.networkMapCallbacks { callbacks = append(callbacks, fn) diff --git a/wgengine/wgcfg/config.go b/wgengine/wgcfg/config.go index 16b75969f..c8285a74f 100644 --- a/wgengine/wgcfg/config.go +++ b/wgengine/wgcfg/config.go @@ -37,6 +37,7 @@ type Peer struct { PublicKey key.NodePublic DiscoKey key.DiscoPublic // present only so we can handle restarts within wgengine, not passed to WireGuard AllowedIPs []netip.Prefix + V4MasqAddr netip.Addr // if non-zero, masquerade IPv4 traffic to this peer using this address PersistentKeepalive uint16 // wireguard-go's endpoint for this peer. It should always equal Peer.PublicKey. // We represent it explicitly so that we can detect if they diverge and recover. diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go index 83640ebef..d07f7232b 100644 --- a/wgengine/wgcfg/nmcfg/nmcfg.go +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -101,6 +101,7 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags, } didExitNodeWarn := false + cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer for _, allowedIP := range peer.AllowedIPs { if allowedIP.Bits() == 0 && peer.StableID != exitNode { if didExitNodeWarn { diff --git a/wgengine/wgcfg/wgcfg_clone.go b/wgengine/wgcfg/wgcfg_clone.go index 35fb018cb..d87f4487c 100644 --- a/wgengine/wgcfg/wgcfg_clone.go +++ b/wgengine/wgcfg/wgcfg_clone.go @@ -62,6 +62,7 @@ var _PeerCloneNeedsRegeneration = Peer(struct { PublicKey key.NodePublic DiscoKey key.DiscoPublic AllowedIPs []netip.Prefix + V4MasqAddr netip.Addr PersistentKeepalive uint16 WGEndpoint key.NodePublic }{})