@@ -21,15 +21,17 @@ import (
21
21
)
22
22
23
23
const (
24
- client1Port = 48001
25
- client1RouterPort = 48011
26
- client2Port = 48002
27
- client2RouterPort = 48012
24
+ client1Port = 48001
25
+ client1RouterPort = 48011 // used in easy and hard NAT
26
+ client1RouterPortSTUN = 48201 // used in hard NAT
27
+ client2Port = 48002
28
+ client2RouterPort = 48012 // used in easy and hard NAT
29
+ client2RouterPortSTUN = 48101 // used in hard NAT
28
30
)
29
31
30
32
type TestNetworking struct {
31
33
Server TestNetworkingServer
32
- STUN TestNetworkingSTUN
34
+ STUNs [] TestNetworkingSTUN
33
35
Client1 TestNetworkingClient
34
36
Client2 TestNetworkingClient
35
37
}
@@ -40,8 +42,8 @@ type TestNetworkingServer struct {
40
42
}
41
43
42
44
type TestNetworkingSTUN struct {
43
- Process TestNetworkingProcess
44
- // If empty, no STUN subprocess is launched.
45
+ Process TestNetworkingProcess
46
+ IP string
45
47
ListenAddr string
46
48
}
47
49
@@ -169,53 +171,82 @@ func SetupNetworkingEasyNAT(t *testing.T, _ slog.Logger) TestNetworking {
169
171
// also creates a namespace and bridge address for a STUN server.
170
172
func SetupNetworkingEasyNATWithSTUN (t * testing.T , _ slog.Logger ) TestNetworking {
171
173
internet := easyNAT (t )
174
+ internet .Net .STUNs = []TestNetworkingSTUN {
175
+ prepareSTUNServer (t , & internet , 0 ),
176
+ }
172
177
173
- // Create another network namespace for the STUN server.
174
- stunNetNS := createNetNS (t , internet .NamePrefix + "stun" )
175
- internet .Net .STUN .Process = TestNetworkingProcess {
176
- NetNS : stunNetNS ,
178
+ return internet .Net
179
+ }
180
+
181
+ // hardNAT creates a fake internet with multiple STUN servers and sets up "hard
182
+ // NAT" forwarding rules. If bothHard is false, only the first client will have
183
+ // hard NAT rules, and the second client will have easy NAT rules.
184
+ //
185
+ //nolint:revive
186
+ func hardNAT (t * testing.T , stunCount int , bothHard bool ) fakeInternet {
187
+ internet := createFakeInternet (t )
188
+ internet .Net .STUNs = make ([]TestNetworkingSTUN , stunCount )
189
+ for i := 0 ; i < stunCount ; i ++ {
190
+ internet .Net .STUNs [i ] = prepareSTUNServer (t , & internet , i )
177
191
}
178
192
179
- const ip = "10.0.0.64"
180
- err := joinBridge (joinBridgeOpts {
181
- bridgeNetNS : internet .BridgeNetNS ,
182
- netNS : stunNetNS ,
183
- bridgeName : internet .BridgeName ,
184
- vethPair : vethPair {
185
- Outer : internet .NamePrefix + "b-stun" ,
186
- Inner : internet .NamePrefix + "stun-b" ,
187
- },
188
- ip : ip ,
189
- })
190
- require .NoError (t , err , "join bridge with STUN server" )
191
- internet .Net .STUN .ListenAddr = ip + ":3478"
193
+ _ , err := commandInNetNS (internet .BridgeNetNS , "sysctl" , []string {"-w" , "net.ipv4.ip_forward=1" }).Output ()
194
+ require .NoError (t , wrapExitErr (err ), "enable IP forwarding in bridge NetNS" )
192
195
193
- // Define custom DERP map.
194
- stunRegion := & tailcfg.DERPRegion {
195
- RegionID : 10000 ,
196
- RegionCode : "stun0" ,
197
- RegionName : "STUN0" ,
198
- Nodes : []* tailcfg.DERPNode {
199
- {
200
- Name : "stun0a" ,
201
- RegionID : 1 ,
202
- IPv4 : ip ,
203
- IPv6 : "none" ,
204
- STUNPort : 3478 ,
205
- STUNOnly : true ,
206
- },
196
+ // Set up iptables masquerade rules to allow each router to NAT packets.
197
+ leaves := []struct {
198
+ fakeRouterLeaf
199
+ peerIP string
200
+ clientPort int
201
+ natPortPeer int
202
+ natStartPortSTUN int
203
+ }{
204
+ {
205
+ fakeRouterLeaf : internet .Client1 ,
206
+ peerIP : internet .Client2 .RouterIP ,
207
+ clientPort : client1Port ,
208
+ natPortPeer : client1RouterPort ,
209
+ natStartPortSTUN : client1RouterPortSTUN ,
210
+ },
211
+ {
212
+ fakeRouterLeaf : internet .Client2 ,
213
+ // If peerIP is empty, we do easy NAT (even for STUN)
214
+ peerIP : func () string {
215
+ if bothHard {
216
+ return internet .Client1 .RouterIP
217
+ }
218
+ return ""
219
+ }(),
220
+ clientPort : client2Port ,
221
+ natPortPeer : client2RouterPort ,
222
+ natStartPortSTUN : client2RouterPortSTUN ,
207
223
},
208
224
}
209
- client1DERP , err := internet .Net .Client1 .ResolveDERPMap ()
210
- require .NoError (t , err , "resolve DERP map for client 1" )
211
- client1DERP .Regions [stunRegion .RegionID ] = stunRegion
212
- internet .Net .Client1 .DERPMap = client1DERP
213
- client2DERP , err := internet .Net .Client2 .ResolveDERPMap ()
214
- require .NoError (t , err , "resolve DERP map for client 2" )
215
- client2DERP .Regions [stunRegion .RegionID ] = stunRegion
216
- internet .Net .Client2 .DERPMap = client2DERP
225
+ for _ , leaf := range leaves {
226
+ _ , err := commandInNetNS (leaf .RouterNetNS , "sysctl" , []string {"-w" , "net.ipv4.ip_forward=1" }).Output ()
227
+ require .NoError (t , wrapExitErr (err ), "enable IP forwarding in router NetNS" )
217
228
218
- return internet .Net
229
+ // All non-UDP traffic should use regular masquerade e.g. for HTTP.
230
+ iptablesMasqueradeNonUDP (t , leaf .RouterNetNS )
231
+
232
+ // NAT from this client to its peer.
233
+ iptablesNAT (t , leaf .RouterNetNS , leaf .ClientIP , leaf .clientPort , leaf .RouterIP , leaf .natPortPeer , leaf .peerIP )
234
+
235
+ // NAT from this client to each STUN server. Only do this if we're doing
236
+ // hard NAT, as the rule above will also touch STUN traffic in easy NAT.
237
+ if leaf .peerIP != "" {
238
+ for i , stun := range internet .Net .STUNs {
239
+ natPort := leaf .natStartPortSTUN + i
240
+ iptablesNAT (t , leaf .RouterNetNS , leaf .ClientIP , leaf .clientPort , leaf .RouterIP , natPort , stun .IP )
241
+ }
242
+ }
243
+ }
244
+
245
+ return internet
246
+ }
247
+
248
+ func SetupNetworkingHardNATEasyNATDirect (t * testing.T , _ slog.Logger ) TestNetworking {
249
+ return hardNAT (t , 2 , false ).Net
219
250
}
220
251
221
252
type vethPair struct {
@@ -600,6 +631,119 @@ func addRouteInNetNS(netNS *os.File, route []string) error {
600
631
return nil
601
632
}
602
633
634
+ // prepareSTUNServer creates a STUN server networking spec in a network
635
+ // namespace and joins it to the bridge. It also sets up the DERP map for the
636
+ // clients to use the STUN.
637
+ func prepareSTUNServer (t * testing.T , internet * fakeInternet , number int ) TestNetworkingSTUN {
638
+ name := fmt .Sprintf ("stn%d" , number )
639
+
640
+ stunNetNS := createNetNS (t , internet .NamePrefix + name )
641
+ stun := TestNetworkingSTUN {
642
+ Process : TestNetworkingProcess {
643
+ NetNS : stunNetNS ,
644
+ },
645
+ }
646
+
647
+ stun .IP = "10.0.0." + fmt .Sprint (64 + number )
648
+ err := joinBridge (joinBridgeOpts {
649
+ bridgeNetNS : internet .BridgeNetNS ,
650
+ netNS : stunNetNS ,
651
+ bridgeName : internet .BridgeName ,
652
+ vethPair : vethPair {
653
+ Outer : internet .NamePrefix + "b-" + name ,
654
+ Inner : internet .NamePrefix + name + "-b" ,
655
+ },
656
+ ip : stun .IP ,
657
+ })
658
+ require .NoError (t , err , "join bridge with STUN server" )
659
+ stun .ListenAddr = stun .IP + ":3478"
660
+
661
+ // Define custom DERP map.
662
+ stunRegion := & tailcfg.DERPRegion {
663
+ RegionID : 10000 + number ,
664
+ RegionCode : name ,
665
+ RegionName : name ,
666
+ Nodes : []* tailcfg.DERPNode {
667
+ {
668
+ Name : name + "a" ,
669
+ RegionID : 1 ,
670
+ IPv4 : stun .IP ,
671
+ IPv6 : "none" ,
672
+ STUNPort : 3478 ,
673
+ STUNOnly : true ,
674
+ },
675
+ },
676
+ }
677
+ client1DERP , err := internet .Net .Client1 .ResolveDERPMap ()
678
+ require .NoError (t , err , "resolve DERP map for client 1" )
679
+ client1DERP .Regions [stunRegion .RegionID ] = stunRegion
680
+ internet .Net .Client1 .DERPMap = client1DERP
681
+ client2DERP , err := internet .Net .Client2 .ResolveDERPMap ()
682
+ require .NoError (t , err , "resolve DERP map for client 2" )
683
+ client2DERP .Regions [stunRegion .RegionID ] = stunRegion
684
+ internet .Net .Client2 .DERPMap = client2DERP
685
+
686
+ return stun
687
+ }
688
+
689
+ func iptablesMasqueradeNonUDP (t * testing.T , netNS * os.File ) {
690
+ t .Helper ()
691
+ _ , err := commandInNetNS (netNS , "iptables" , []string {
692
+ "-t" , "nat" ,
693
+ "-A" , "POSTROUTING" ,
694
+ // Every interface except loopback.
695
+ "!" , "-o" , "lo" ,
696
+ // Every protocol except UDP.
697
+ "!" , "-p" , "udp" ,
698
+ "-j" , "MASQUERADE" ,
699
+ }).Output ()
700
+ require .NoError (t , wrapExitErr (err ), "add iptables non-UDP masquerade rule" )
701
+ }
702
+
703
+ // iptablesNAT sets up iptables rules for NAT forwarding. If destIP is
704
+ // specified, the forwarding rule will only apply to traffic to/from that IP
705
+ // (mapvarydest).
706
+ func iptablesNAT (t * testing.T , netNS * os.File , clientIP string , clientPort int , routerIP string , routerPort int , destIP string ) {
707
+ t .Helper ()
708
+
709
+ snatArgs := []string {
710
+ "-t" , "nat" ,
711
+ "-A" , "POSTROUTING" ,
712
+ "-p" , "udp" ,
713
+ "--sport" , fmt .Sprint (clientPort ),
714
+ "-j" , "SNAT" ,
715
+ "--to-source" , fmt .Sprintf ("%s:%d" , routerIP , routerPort ),
716
+ }
717
+ if destIP != "" {
718
+ // Insert `-d $destIP` after the --sport flag+value.
719
+ newSnatArgs := append ([]string {}, snatArgs [:8 ]... )
720
+ newSnatArgs = append (newSnatArgs , "-d" , destIP )
721
+ newSnatArgs = append (newSnatArgs , snatArgs [8 :]... )
722
+ snatArgs = newSnatArgs
723
+ }
724
+ _ , err := commandInNetNS (netNS , "iptables" , snatArgs ).Output ()
725
+ require .NoError (t , wrapExitErr (err ), "add iptables SNAT rule" )
726
+
727
+ // Incoming traffic should be forwarded to the client's IP.
728
+ dnatArgs := []string {
729
+ "-t" , "nat" ,
730
+ "-A" , "PREROUTING" ,
731
+ "-p" , "udp" ,
732
+ "--dport" , fmt .Sprint (routerPort ),
733
+ "-j" , "DNAT" ,
734
+ "--to-destination" , fmt .Sprintf ("%s:%d" , clientIP , clientPort ),
735
+ }
736
+ if destIP != "" {
737
+ // Insert `-s $destIP` before the --dport flag+value.
738
+ newDnatArgs := append ([]string {}, dnatArgs [:6 ]... )
739
+ newDnatArgs = append (newDnatArgs , "-s" , destIP )
740
+ newDnatArgs = append (newDnatArgs , dnatArgs [6 :]... )
741
+ dnatArgs = newDnatArgs
742
+ }
743
+ _ , err = commandInNetNS (netNS , "iptables" , dnatArgs ).Output ()
744
+ require .NoError (t , wrapExitErr (err ), "add iptables DNAT rule" )
745
+ }
746
+
603
747
func commandInNetNS (netNS * os.File , bin string , args []string ) * exec.Cmd {
604
748
//nolint:gosec
605
749
cmd := exec .Command ("nsenter" , append ([]string {"--net=/proc/self/fd/3" , bin }, args ... )... )
0 commit comments