4
4
package integration_test
5
5
6
6
import (
7
+ "context"
8
+ "encoding/json"
7
9
"flag"
8
10
"fmt"
11
+ "net"
9
12
"net/http"
10
13
"net/url"
11
14
"os"
12
15
"os/signal"
16
+ "path/filepath"
13
17
"runtime"
18
+ "strconv"
14
19
"syscall"
15
20
"testing"
16
21
"time"
17
22
18
23
"github.com/google/uuid"
24
+ "github.com/stretchr/testify/assert"
19
25
"github.com/stretchr/testify/require"
26
+ "tailscale.com/net/stun/stuntest"
27
+ "tailscale.com/tailcfg"
28
+ "tailscale.com/types/nettype"
20
29
21
30
"cdr.dev/slog"
22
31
"cdr.dev/slog/sloggers/slogtest"
@@ -30,17 +39,22 @@ const runTestEnv = "CODER_TAILNET_TESTS"
30
39
var (
31
40
isSubprocess = flag .Bool ("subprocess" , false , "Signifies that this is a test subprocess" )
32
41
testID = flag .String ("test-name" , "" , "Which test is being run" )
33
- role = flag .String ("role" , "" , "The role of the test subprocess: server, client" )
42
+ role = flag .String ("role" , "" , "The role of the test subprocess: server, stun, client" )
34
43
35
44
// Role: server
36
45
serverListenAddr = flag .String ("server-listen-addr" , "" , "The address to listen on for the server" )
37
46
47
+ // Role: stun
48
+ stunListenAddr = flag .String ("stun-listen-addr" , "" , "The address to listen on for the STUN server" )
49
+
38
50
// Role: client
39
- clientName = flag .String ("client-name" , "" , "The name of the client for logs" )
40
- clientServerURL = flag .String ("client-server-url" , "" , "The url to connect to the server" )
41
- clientMyID = flag .String ("client-id" , "" , "The id of the client" )
42
- clientPeerID = flag .String ("client-peer-id" , "" , "The id of the other client" )
43
- clientRunTests = flag .Bool ("client-run-tests" , false , "Run the tests in the client subprocess" )
51
+ clientName = flag .String ("client-name" , "" , "The name of the client for logs" )
52
+ clientNumber = flag .Int ("client-number" , 0 , "The number of the client" )
53
+ clientMyID = flag .String ("client-id" , "" , "The id of the client" )
54
+ clientPeerID = flag .String ("client-peer-id" , "" , "The id of the other client" )
55
+ clientServerURL = flag .String ("client-server-url" , "" , "The url to connect to the server" )
56
+ clientDERPMapPath = flag .String ("client-derp-map-path" , "" , "The path to the DERP map file to use on this client" )
57
+ clientRunTests = flag .Bool ("client-run-tests" , false , "Run the tests in the client subprocess" )
44
58
)
45
59
46
60
func TestMain (m * testing.M ) {
@@ -87,7 +101,7 @@ var topologies = []integration.TestTopology{
87
101
// endpoints to connect as routing is enabled between client 1 and
88
102
// client 2.
89
103
Name : "EasyNATDirect" ,
90
- SetupNetworking : integration .SetupNetworkingEasyNAT ,
104
+ SetupNetworking : integration .SetupNetworkingEasyNATWithSTUN ,
91
105
Server : integration.SimpleServerOptions {},
92
106
StartClient : integration .StartClientDirect ,
93
107
RunTests : integration .TestSuite ,
@@ -143,17 +157,41 @@ func TestIntegration(t *testing.T) {
143
157
log := slogtest .Make (t , nil ).Leveled (slog .LevelDebug )
144
158
networking := topo .SetupNetworking (t , log )
145
159
146
- // Fork the three child processes.
160
+ // Useful for debugging network namespaces by avoiding cleanup.
161
+ // t.Cleanup(func() {
162
+ // time.Sleep(time.Minute * 15)
163
+ // })
164
+
147
165
closeServer := startServerSubprocess (t , topo .Name , networking )
166
+
167
+ closeSTUN := func () error { return nil }
168
+ if networking .STUN .ListenAddr != "" {
169
+ closeSTUN = startSTUNSubprocess (t , topo .Name , networking )
170
+ }
171
+
172
+ // Write the DERP maps to a file.
173
+ tempDir := t .TempDir ()
174
+ client1DERPMapPath := filepath .Join (tempDir , "client1-derp-map.json" )
175
+ client1DERPMap , err := networking .Client1 .ResolveDERPMap ()
176
+ require .NoError (t , err , "resolve client 1 DERP map" )
177
+ err = writeDERPMapToFile (client1DERPMapPath , client1DERPMap )
178
+ require .NoError (t , err , "write client 1 DERP map" )
179
+ client2DERPMapPath := filepath .Join (tempDir , "client2-derp-map.json" )
180
+ client2DERPMap , err := networking .Client2 .ResolveDERPMap ()
181
+ require .NoError (t , err , "resolve client 2 DERP map" )
182
+ err = writeDERPMapToFile (client2DERPMapPath , client2DERPMap )
183
+ require .NoError (t , err , "write client 2 DERP map" )
184
+
148
185
// client1 runs the tests.
149
- client1ErrCh , _ := startClientSubprocess (t , topo .Name , networking , 1 )
150
- _ , closeClient2 := startClientSubprocess (t , topo .Name , networking , 2 )
186
+ client1ErrCh , _ := startClientSubprocess (t , topo .Name , networking , 1 , client1DERPMapPath )
187
+ _ , closeClient2 := startClientSubprocess (t , topo .Name , networking , 2 , client2DERPMapPath )
151
188
152
189
// Wait for client1 to exit.
153
190
require .NoError (t , <- client1ErrCh , "client 1 exited" )
154
191
155
192
// Close client2 and the server.
156
193
require .NoError (t , closeClient2 (), "client 2 exited" )
194
+ require .NoError (t , closeSTUN (), "stun exited" )
157
195
require .NoError (t , closeServer (), "server exited" )
158
196
})
159
197
}
@@ -169,10 +207,11 @@ func handleTestSubprocess(t *testing.T) {
169
207
}
170
208
}
171
209
require .NotEmptyf (t , topo .Name , "unknown test topology %q" , * testID )
210
+ require .Contains (t , []string {"server" , "stun" , "client" }, * role , "unknown role %q" , * role )
172
211
173
212
testName := topo .Name + "/"
174
- if * role == "server" {
175
- testName += "server"
213
+ if * role == "server" || * role == "stun" {
214
+ testName += * role
176
215
} else {
177
216
testName += * clientName
178
217
}
@@ -185,18 +224,34 @@ func handleTestSubprocess(t *testing.T) {
185
224
topo .Server .StartServer (t , logger , * serverListenAddr )
186
225
// no exit
187
226
227
+ case "stun" :
228
+ launchSTUNServer (t , * stunListenAddr )
229
+ // no exit
230
+
188
231
case "client" :
189
232
logger = logger .Named (* clientName )
233
+ if * clientNumber != 1 && * clientNumber != 2 {
234
+ t .Fatalf ("invalid client number %d" , clientNumber )
235
+ }
190
236
serverURL , err := url .Parse (* clientServerURL )
191
237
require .NoErrorf (t , err , "parse server url %q" , * clientServerURL )
192
238
myID , err := uuid .Parse (* clientMyID )
193
239
require .NoErrorf (t , err , "parse client id %q" , * clientMyID )
194
240
peerID , err := uuid .Parse (* clientPeerID )
195
241
require .NoErrorf (t , err , "parse peer id %q" , * clientPeerID )
196
242
243
+ // Load the DERP map.
244
+ var derpMap tailcfg.DERPMap
245
+ derpMapPath := * clientDERPMapPath
246
+ f , err := os .Open (derpMapPath )
247
+ require .NoErrorf (t , err , "open DERP map %q" , derpMapPath )
248
+ err = json .NewDecoder (f ).Decode (& derpMap )
249
+ _ = f .Close ()
250
+ require .NoErrorf (t , err , "decode DERP map %q" , derpMapPath )
251
+
197
252
waitForServerAvailable (t , serverURL )
198
253
199
- conn := topo .StartClient (t , logger , serverURL , myID , peerID )
254
+ conn := topo .StartClient (t , logger , serverURL , & derpMap , * clientNumber , myID , peerID )
200
255
201
256
if * clientRunTests {
202
257
// Wait for connectivity.
@@ -218,6 +273,23 @@ func handleTestSubprocess(t *testing.T) {
218
273
})
219
274
}
220
275
276
+ type forcedAddrPacketListener struct {
277
+ addr string
278
+ }
279
+
280
+ var _ nettype.PacketListener = forcedAddrPacketListener {}
281
+
282
+ func (ln forcedAddrPacketListener ) ListenPacket (ctx context.Context , network , _ string ) (net.PacketConn , error ) {
283
+ return nettype.Std {}.ListenPacket (ctx , network , ln .addr )
284
+ }
285
+
286
+ func launchSTUNServer (t * testing.T , listenAddr string ) {
287
+ ln := forcedAddrPacketListener {addr : listenAddr }
288
+ addr , cleanup := stuntest .ServeWithPacketListener (t , ln )
289
+ t .Cleanup (cleanup )
290
+ assert .Equal (t , listenAddr , addr .String (), "listen address should match forced addr" )
291
+ }
292
+
221
293
func waitForServerAvailable (t * testing.T , serverURL * url.URL ) {
222
294
const delay = 100 * time .Millisecond
223
295
const reqTimeout = 2 * time .Second
@@ -247,45 +319,55 @@ func waitForServerAvailable(t *testing.T, serverURL *url.URL) {
247
319
}
248
320
249
321
func startServerSubprocess (t * testing.T , topologyName string , networking integration.TestNetworking ) func () error {
250
- _ , closeFn := startSubprocess (t , "server" , networking .ProcessServer .NetNS , []string {
322
+ _ , closeFn := startSubprocess (t , "server" , networking .Server . Process .NetNS , []string {
251
323
"--subprocess" ,
252
324
"--test-name=" + topologyName ,
253
325
"--role=server" ,
254
- "--server-listen-addr=" + networking .ServerListenAddr ,
326
+ "--server-listen-addr=" + networking .Server .ListenAddr ,
327
+ })
328
+ return closeFn
329
+ }
330
+
331
+ func startSTUNSubprocess (t * testing.T , topologyName string , networking integration.TestNetworking ) func () error {
332
+ _ , closeFn := startSubprocess (t , "stun" , networking .STUN .Process .NetNS , []string {
333
+ "--subprocess" ,
334
+ "--test-name=" + topologyName ,
335
+ "--role=stun" ,
336
+ "--stun-listen-addr=" + networking .STUN .ListenAddr ,
255
337
})
256
338
return closeFn
257
339
}
258
340
259
- func startClientSubprocess (t * testing.T , topologyName string , networking integration.TestNetworking , clientNumber int ) (<- chan error , func () error ) {
341
+ func startClientSubprocess (t * testing.T , topologyName string , networking integration.TestNetworking , clientNumber int , derpMapPath string ) (<- chan error , func () error ) {
260
342
require .True (t , clientNumber == 1 || clientNumber == 2 )
261
343
262
344
var (
263
- clientName = fmt .Sprintf ("client%d" , clientNumber )
264
- myID = integration .Client1ID
265
- peerID = integration .Client2ID
266
- accessURL = networking .ServerAccessURLClient1
267
- netNS = networking .ProcessClient1 .NetNS
345
+ clientName = fmt .Sprintf ("client%d" , clientNumber )
346
+ myID = integration .Client1ID
347
+ peerID = integration .Client2ID
348
+ clientConfig = networking .Client1
268
349
)
269
350
if clientNumber == 2 {
270
351
myID , peerID = peerID , myID
271
- accessURL = networking .ServerAccessURLClient2
272
- netNS = networking .ProcessClient2 .NetNS
352
+ clientConfig = networking .Client2
273
353
}
274
354
275
355
flags := []string {
276
356
"--subprocess" ,
277
357
"--test-name=" + topologyName ,
278
358
"--role=client" ,
279
359
"--client-name=" + clientName ,
280
- "--client-server-url=" + accessURL ,
360
+ "--client-number=" + strconv .Itoa (clientNumber ),
361
+ "--client-server-url=" + clientConfig .ServerAccessURL ,
281
362
"--client-id=" + myID .String (),
282
363
"--client-peer-id=" + peerID .String (),
364
+ "--client-derp-map-path=" + derpMapPath ,
283
365
}
284
366
if clientNumber == 1 {
285
367
flags = append (flags , "--client-run-tests" )
286
368
}
287
369
288
- return startSubprocess (t , clientName , netNS , flags )
370
+ return startSubprocess (t , clientName , clientConfig . Process . NetNS , flags )
289
371
}
290
372
291
373
// startSubprocess launches the test binary with the same flags as the test, but
@@ -298,3 +380,19 @@ func startSubprocess(t *testing.T, processName string, netNS *os.File, flags []s
298
380
args := append (os .Args [1 :], append ([]string {"-test.v=true" }, flags ... )... )
299
381
return integration .ExecBackground (t , processName , netNS , name , args )
300
382
}
383
+
384
+ func writeDERPMapToFile (path string , derpMap * tailcfg.DERPMap ) error {
385
+ f , err := os .Create (path )
386
+ if err != nil {
387
+ return err
388
+ }
389
+ defer f .Close ()
390
+
391
+ enc := json .NewEncoder (f )
392
+ enc .SetIndent ("" , " " )
393
+ err = enc .Encode (derpMap )
394
+ if err != nil {
395
+ return err
396
+ }
397
+ return nil
398
+ }
0 commit comments