Skip to content

Commit 985535a

Browse files
Maisem Alimaisem
authored andcommitted
net/tstun,wgengine/*: add support for NAT to routes
This adds support to make exit nodes and subnet routers work when in scenarios where NAT is required. It also updates the NATConfig to be generated from a `wgcfg.Config` as that handles merging prefs with the netmap, so it has the required information about whether an exit node is already configured and whether routes are accepted. Updates tailscale/corp#8020 Signed-off-by: Maisem Ali <[email protected]>
1 parent d1d5d52 commit 985535a

File tree

7 files changed

+103
-47
lines changed

7 files changed

+103
-47
lines changed

cmd/tailscaled/depaware.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
256256
tailscale.com/net/tsdial from tailscale.com/control/controlclient+
257257
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
258258
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
259+
tailscale.com/net/tstun/table from tailscale.com/net/tstun
259260
tailscale.com/net/wsconn from tailscale.com/control/controlhttp+
260261
tailscale.com/paths from tailscale.com/ipn/ipnlocal+
261262
💣 tailscale.com/portlist from tailscale.com/ipn/ipnlocal
@@ -264,6 +265,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
264265
LD 💣 tailscale.com/ssh/tailssh from tailscale.com/cmd/tailscaled
265266
tailscale.com/syncs from tailscale.com/net/netcheck+
266267
tailscale.com/tailcfg from tailscale.com/client/tailscale/apitype+
268+
💣 tailscale.com/tempfork/device from tailscale.com/net/tstun/table
267269
LD tailscale.com/tempfork/gliderlabs/ssh from tailscale.com/ssh/tailssh
268270
tailscale.com/tka from tailscale.com/ipn/ipnlocal+
269271
W tailscale.com/tsconst from tailscale.com/net/interfaces

net/tstun/wrap.go

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,18 @@ import (
2525
"tailscale.com/net/connstats"
2626
"tailscale.com/net/packet"
2727
"tailscale.com/net/tsaddr"
28+
"tailscale.com/net/tstun/table"
2829
"tailscale.com/syncs"
2930
"tailscale.com/tstime/mono"
3031
"tailscale.com/types/ipproto"
3132
"tailscale.com/types/key"
3233
"tailscale.com/types/logger"
33-
"tailscale.com/types/netmap"
3434
"tailscale.com/types/views"
3535
"tailscale.com/util/clientmetric"
3636
"tailscale.com/util/mak"
3737
"tailscale.com/wgengine/capture"
3838
"tailscale.com/wgengine/filter"
39+
"tailscale.com/wgengine/wgcfg"
3940
)
4041

4142
const maxBufferSize = device.MaxMessageSize
@@ -522,10 +523,12 @@ type natV4Config struct {
522523
// dstMasqAddrs is map of dst addresses to their respective MasqueradeAsIP
523524
// addresses. The MasqueradeAsIP address is the address that should be used
524525
// as the source address for packets to dst.
525-
dstMasqAddrs views.Map[netip.Addr, netip.Addr] // dst -> masqAddr
526+
dstMasqAddrs views.Map[key.NodePublic, netip.Addr] // dst -> masqAddr
526527

527-
// TODO(maisem/nyghtowl): add support for subnets and exit nodes and test them.
528-
// Determine IP routing table algorithm to use - e.g. ART?
528+
// dstAddrToPeerKeyMapper is the routing table used to map a given dst IP to
529+
// the peer key responsible for that IP.
530+
// It only contains peers that require a MasqueradeAsIP address.
531+
dstAddrToPeerKeyMapper *table.RoutingTable
529532
}
530533

531534
// 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 {
550553
if oldSrc != c.nativeAddr {
551554
return oldSrc
552555
}
553-
if eip, ok := c.dstMasqAddrs.GetOk(dst); ok {
556+
p, ok := c.dstAddrToPeerKeyMapper.Lookup(dst)
557+
if !ok {
558+
return oldSrc
559+
}
560+
if eip, ok := c.dstMasqAddrs.GetOk(p); ok {
554561
return eip
555562
}
556563
return oldSrc
557564
}
558565

559-
// natConfigFromNetMap generates a natV4Config from nm.
566+
// natConfigFromWireGuardConfig generates a natV4Config from nm.
560567
// If v4 NAT is not required, it returns nil.
561-
func natConfigFromNetMap(nm *netmap.NetworkMap) *natV4Config {
562-
if nm == nil || nm.SelfNode == nil {
568+
func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
569+
if wcfg == nil {
563570
return nil
564571
}
565-
nativeAddr := findV4(nm.SelfNode.Addresses)
572+
nativeAddr := findV4(wcfg.Addresses)
566573
if !nativeAddr.IsValid() {
567574
return nil
568575
}
569576
var (
570-
dstMasqAddrs map[netip.Addr]netip.Addr
577+
rt table.RoutingTableBuilder
578+
dstMasqAddrs map[key.NodePublic]netip.Addr
571579
listenAddrs map[netip.Addr]struct{}
572580
)
573-
for _, p := range nm.Peers {
574-
if !p.SelfNodeV4MasqAddrForThisPeer.IsValid() {
575-
continue
576-
}
577-
peerV4 := findV4(p.Addresses)
578-
if !peerV4.IsValid() {
581+
for i := range wcfg.Peers {
582+
p := &wcfg.Peers[i]
583+
if !p.V4MasqAddr.IsValid() {
579584
continue
580585
}
581-
mak.Set(&dstMasqAddrs, peerV4, p.SelfNodeV4MasqAddrForThisPeer)
582-
mak.Set(&listenAddrs, p.SelfNodeV4MasqAddrForThisPeer, struct{}{})
586+
rt.InsertOrReplace(p.PublicKey, p.AllowedIPs...)
587+
mak.Set(&dstMasqAddrs, p.PublicKey, p.V4MasqAddr)
588+
mak.Set(&listenAddrs, p.V4MasqAddr, struct{}{})
583589
}
584590
if len(listenAddrs) == 0 || len(dstMasqAddrs) == 0 {
585591
return nil
586592
}
587593
return &natV4Config{
588-
nativeAddr: nativeAddr,
589-
listenAddrs: views.MapOf(listenAddrs),
590-
dstMasqAddrs: views.MapOf(dstMasqAddrs),
594+
nativeAddr: nativeAddr,
595+
listenAddrs: views.MapOf(listenAddrs),
596+
dstMasqAddrs: views.MapOf(dstMasqAddrs),
597+
dstAddrToPeerKeyMapper: rt.Build(),
591598
}
592599
}
593600

594601
// SetNetMap is called when a new NetworkMap is received.
595602
// It currently (2023-03-01) only updates the IPv4 NAT configuration.
596-
func (t *Wrapper) SetNetMap(nm *netmap.NetworkMap) {
597-
cfg := natConfigFromNetMap(nm)
603+
func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) {
604+
cfg := natConfigFromWGConfig(wcfg)
598605
old := t.natV4Config.Swap(cfg)
599606
if !reflect.DeepEqual(old, cfg) {
600607
t.logf("nat config: %+v", cfg)

net/tstun/wrap_test.go

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,15 @@ import (
2525
"tailscale.com/net/connstats"
2626
"tailscale.com/net/netaddr"
2727
"tailscale.com/net/packet"
28-
"tailscale.com/tailcfg"
2928
"tailscale.com/tstest"
3029
"tailscale.com/tstime/mono"
3130
"tailscale.com/types/ipproto"
3231
"tailscale.com/types/key"
3332
"tailscale.com/types/logger"
3433
"tailscale.com/types/netlogtype"
35-
"tailscale.com/types/netmap"
3634
"tailscale.com/util/must"
3735
"tailscale.com/wgengine/filter"
36+
"tailscale.com/wgengine/wgcfg"
3837
)
3938

4039
func udp4(src, dst string, sport, dport uint16) []byte {
@@ -597,13 +596,16 @@ func TestFilterDiscoLoop(t *testing.T) {
597596
}
598597

599598
func TestNATCfg(t *testing.T) {
600-
node := func(ip, eip netip.Addr) *tailcfg.Node {
601-
return &tailcfg.Node{
602-
Addresses: []netip.Prefix{
599+
node := func(ip, eip netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
600+
p := wgcfg.Peer{
601+
PublicKey: key.NewNode().Public(),
602+
AllowedIPs: []netip.Prefix{
603603
netip.PrefixFrom(ip, ip.BitLen()),
604604
},
605-
SelfNodeV4MasqAddrForThisPeer: eip,
605+
V4MasqAddr: eip,
606606
}
607+
p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...)
608+
return p
607609
}
608610
var (
609611
noIP netip.Addr
@@ -615,20 +617,20 @@ func TestNATCfg(t *testing.T) {
615617
peer1IP = netip.MustParseAddr("100.64.0.2")
616618
peer2IP = netip.MustParseAddr("100.64.0.3")
617619

618-
// subnets should not be impacted.
619-
// TODO(maisem/nyghtowl): add support for subnets and exit nodes and test them.
620620
subnet = netip.MustParseAddr("192.168.0.1")
621+
622+
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
621623
)
622624

623625
tests := []struct {
624626
name string
625-
nm *netmap.NetworkMap
627+
wcfg *wgcfg.Config
626628
snatMap map[netip.Addr]netip.Addr // dst -> src
627629
dnatMap map[netip.Addr]netip.Addr
628630
}{
629631
{
630-
name: "no-netmap",
631-
nm: nil,
632+
name: "no-cfg",
633+
wcfg: nil,
632634
snatMap: map[netip.Addr]netip.Addr{
633635
peer1IP: selfNativeIP,
634636
peer2IP: selfNativeIP,
@@ -642,9 +644,9 @@ func TestNATCfg(t *testing.T) {
642644
},
643645
{
644646
name: "single-peer-requires-nat",
645-
nm: &netmap.NetworkMap{
646-
SelfNode: node(selfNativeIP, noIP),
647-
Peers: []*tailcfg.Node{
647+
wcfg: &wgcfg.Config{
648+
Addresses: selfAddrs,
649+
Peers: []wgcfg.Peer{
648650
node(peer1IP, noIP),
649651
node(peer2IP, selfEIP1),
650652
},
@@ -663,9 +665,9 @@ func TestNATCfg(t *testing.T) {
663665
},
664666
{
665667
name: "multiple-peers-require-nat",
666-
nm: &netmap.NetworkMap{
667-
SelfNode: node(selfNativeIP, noIP),
668-
Peers: []*tailcfg.Node{
668+
wcfg: &wgcfg.Config{
669+
Addresses: selfAddrs,
670+
Peers: []wgcfg.Peer{
669671
node(peer1IP, selfEIP1),
670672
node(peer2IP, selfEIP2),
671673
},
@@ -682,11 +684,53 @@ func TestNATCfg(t *testing.T) {
682684
subnet: subnet,
683685
},
684686
},
687+
{
688+
name: "multiple-peers-require-nat-with-subnet",
689+
wcfg: &wgcfg.Config{
690+
Addresses: selfAddrs,
691+
Peers: []wgcfg.Peer{
692+
node(peer1IP, selfEIP1),
693+
node(peer2IP, selfEIP2, netip.MustParsePrefix("192.168.0.0/24")),
694+
},
695+
},
696+
snatMap: map[netip.Addr]netip.Addr{
697+
peer1IP: selfEIP1,
698+
peer2IP: selfEIP2,
699+
subnet: selfEIP2,
700+
},
701+
dnatMap: map[netip.Addr]netip.Addr{
702+
selfNativeIP: selfNativeIP,
703+
selfEIP1: selfNativeIP,
704+
selfEIP2: selfNativeIP,
705+
subnet: subnet,
706+
},
707+
},
708+
{
709+
name: "multiple-peers-require-nat-with-default-route",
710+
wcfg: &wgcfg.Config{
711+
Addresses: selfAddrs,
712+
Peers: []wgcfg.Peer{
713+
node(peer1IP, selfEIP1),
714+
node(peer2IP, selfEIP2, netip.MustParsePrefix("0.0.0.0/0")),
715+
},
716+
},
717+
snatMap: map[netip.Addr]netip.Addr{
718+
peer1IP: selfEIP1,
719+
peer2IP: selfEIP2,
720+
netip.MustParseAddr("8.8.8.8"): selfEIP2,
721+
},
722+
dnatMap: map[netip.Addr]netip.Addr{
723+
selfNativeIP: selfNativeIP,
724+
selfEIP1: selfNativeIP,
725+
selfEIP2: selfNativeIP,
726+
subnet: subnet,
727+
},
728+
},
685729
{
686730
name: "no-nat",
687-
nm: &netmap.NetworkMap{
688-
SelfNode: node(selfNativeIP, noIP),
689-
Peers: []*tailcfg.Node{
731+
wcfg: &wgcfg.Config{
732+
Addresses: selfAddrs,
733+
Peers: []wgcfg.Peer{
690734
node(peer1IP, noIP),
691735
node(peer2IP, noIP),
692736
},
@@ -707,15 +751,15 @@ func TestNATCfg(t *testing.T) {
707751

708752
for _, tc := range tests {
709753
t.Run(tc.name, func(t *testing.T) {
710-
ncfg := natConfigFromNetMap(tc.nm)
754+
ncfg := natConfigFromWGConfig(tc.wcfg)
711755
for peer, want := range tc.snatMap {
712756
if got := ncfg.selectSrcIP(selfNativeIP, peer); got != want {
713-
t.Errorf("selectSrcIP: got %v; want %v", got, want)
757+
t.Errorf("selectSrcIP[%v]: got %v; want %v", peer, got, want)
714758
}
715759
}
716760
for dstIP, want := range tc.dnatMap {
717761
if got := ncfg.mapDstIP(dstIP); got != want {
718-
t.Errorf("mapDstIP: got %v; want %v", got, want)
762+
t.Errorf("mapDstIP[%v]: got %v; want %v", dstIP, got, want)
719763
}
720764
}
721765
})

wgengine/userspace.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,7 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
807807

808808
e.wgLock.Lock()
809809
defer e.wgLock.Unlock()
810+
e.tundev.SetWGConfig(cfg)
810811
e.lastDNSConfig = dnsCfg
811812

812813
peerSet := make(map[key.NodePublic]struct{}, len(cfg.Peers))
@@ -1205,7 +1206,6 @@ func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) {
12051206
e.magicConn.SetNetworkMap(nm)
12061207
e.mu.Lock()
12071208
e.netMap = nm
1208-
e.tundev.SetNetMap(nm)
12091209
callbacks := make([]NetworkMapCallback, 0, 4)
12101210
for _, fn := range e.networkMapCallbacks {
12111211
callbacks = append(callbacks, fn)

wgengine/wgcfg/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Peer struct {
3737
PublicKey key.NodePublic
3838
DiscoKey key.DiscoPublic // present only so we can handle restarts within wgengine, not passed to WireGuard
3939
AllowedIPs []netip.Prefix
40+
V4MasqAddr netip.Addr // if non-zero, masquerade IPv4 traffic to this peer using this address
4041
PersistentKeepalive uint16
4142
// wireguard-go's endpoint for this peer. It should always equal Peer.PublicKey.
4243
// We represent it explicitly so that we can detect if they diverge and recover.

wgengine/wgcfg/nmcfg/nmcfg.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags,
101101
}
102102

103103
didExitNodeWarn := false
104+
cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer
104105
for _, allowedIP := range peer.AllowedIPs {
105106
if allowedIP.Bits() == 0 && peer.StableID != exitNode {
106107
if didExitNodeWarn {

wgengine/wgcfg/wgcfg_clone.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)