@@ -28,8 +28,10 @@ import (
28
28
"golang.org/x/xerrors"
29
29
"tailscale.com/derp"
30
30
"tailscale.com/derp/derphttp"
31
+ "tailscale.com/net/packet"
31
32
"tailscale.com/tailcfg"
32
33
"tailscale.com/types/key"
34
+ "tailscale.com/wgengine/capture"
33
35
34
36
"cdr.dev/slog"
35
37
"github.com/coder/coder/v2/coderd/httpapi"
@@ -54,35 +56,36 @@ type Client struct {
54
56
ID uuid.UUID
55
57
ListenPort uint16
56
58
ShouldRunTests bool
59
+ TunnelSrc bool
57
60
}
58
61
59
62
var Client1 = Client {
60
63
Number :ClientNumber1 ,
61
64
ID :uuid .MustParse ("00000000-0000-0000-0000-000000000001" ),
62
65
ListenPort :client1Port ,
63
66
ShouldRunTests :true ,
67
+ TunnelSrc :true ,
64
68
}
65
69
66
70
var Client2 = Client {
67
71
Number :ClientNumber2 ,
68
72
ID :uuid .MustParse ("00000000-0000-0000-0000-000000000002" ),
69
73
ListenPort :client2Port ,
70
74
ShouldRunTests :false ,
75
+ TunnelSrc :false ,
71
76
}
72
77
73
78
type TestTopology struct {
74
79
Name string
75
- // SetupNetworking creates interfaces and network namespaces for the test.
76
- // The most simple implementation is NetworkSetupDefault, which only creates
77
- // a network namespace shared for all tests.
78
- SetupNetworking func (t * testing.T ,logger slog.Logger )TestNetworking
80
+
81
+ NetworkingProvider NetworkingProvider
79
82
80
83
// Server is the server starter for the test. It is executed in the server
81
84
// subprocess.
82
85
Server ServerStarter
83
- // StartClient gets called in each client subprocess. It's expected to
86
+ //ClientStarter. StartClient gets called in each client subprocess. It's expected to
84
87
// create the tailnet.Conn and ensure connectivity to it's peer.
85
- StartClient func ( t * testing. T , logger slog. Logger , serverURL * url. URL , derpMap * tailcfg. DERPMap , me Client , peer Client ) * tailnet. Conn
88
+ ClientStarter ClientStarter
86
89
87
90
// RunTests is the main test function. It's called in each of the client
88
91
// subprocesses. If tests can only run once, they should check the client ID
@@ -97,6 +100,17 @@ type ServerStarter interface {
97
100
StartServer (t * testing.T ,logger slog.Logger ,listenAddr string )
98
101
}
99
102
103
+ type NetworkingProvider interface {
104
+ // SetupNetworking creates interfaces and network namespaces for the test.
105
+ // The most simple implementation is NetworkSetupDefault, which only creates
106
+ // a network namespace shared for all tests.
107
+ SetupNetworking (t * testing.T ,logger slog.Logger )TestNetworking
108
+ }
109
+
110
+ type ClientStarter interface {
111
+ StartClient (t * testing.T ,logger slog.Logger ,serverURL * url.URL ,derpMap * tailcfg.DERPMap ,me Client ,peer Client )* tailnet.Conn
112
+ }
113
+
100
114
type SimpleServerOptions struct {
101
115
// FailUpgradeDERP will make the DERP server fail to handle the initial DERP
102
116
// upgrade in a way that causes the client to fallback to
@@ -369,77 +383,107 @@ http {
369
383
_ ,_ = ExecBackground (t ,"server.nginx" ,nil ,"nginx" , []string {"-c" ,cfgPath })
370
384
}
371
385
372
- // StartClientDERP creates a client connection to the server for coordination
373
- // and creates a tailnet.Conn which will only use DERP to connect to the peer.
374
- func StartClientDERP (t * testing.T ,logger slog.Logger ,serverURL * url.URL ,derpMap * tailcfg.DERPMap ,me ,peer Client )* tailnet.Conn {
375
- return startClientOptions (t ,logger ,serverURL ,me ,peer ,& tailnet.Options {
376
- Addresses : []netip.Prefix {tailnet .TailscaleServicePrefix .PrefixFromUUID (me .ID )},
377
- DERPMap :derpMap ,
378
- BlockEndpoints :true ,
379
- Logger :logger ,
380
- DERPForceWebSockets :false ,
381
- ListenPort :me .ListenPort ,
382
- // These tests don't have internet connection, so we need to force
383
- // magicsock to do anything.
384
- ForceNetworkUp :true ,
385
- })
386
+ type BasicClientStarter struct {
387
+ BlockEndpoints bool
388
+ DERPForceWebsockets bool
389
+ // WaitForConnection means wait for (any) peer connection before returning from StartClient
390
+ WaitForConnection bool
391
+ // WaitForConnection means wait for a direct peer connection before returning from StartClient
392
+ WaitForDirect bool
393
+ // Service is a network service (e.g. an echo server) to start on the client. If Wait* is set, the service is
394
+ // started prior to waiting.
395
+ Service NetworkService
396
+ LogPackets bool
386
397
}
387
398
388
- // StartClientDERPWebSockets does the same thing as StartClientDERP but will
389
- // only use DERP WebSocket fallback.
390
- func StartClientDERPWebSockets (t * testing.T ,logger slog.Logger ,serverURL * url.URL ,derpMap * tailcfg.DERPMap ,me ,peer Client )* tailnet.Conn {
391
- return startClientOptions (t ,logger ,serverURL ,me ,peer ,& tailnet.Options {
392
- Addresses : []netip.Prefix {tailnet .TailscaleServicePrefix .PrefixFromUUID (me .ID )},
393
- DERPMap :derpMap ,
394
- BlockEndpoints :true ,
395
- Logger :logger ,
396
- DERPForceWebSockets :true ,
397
- ListenPort :me .ListenPort ,
398
- // These tests don't have internet connection, so we need to force
399
- // magicsock to do anything.
400
- ForceNetworkUp :true ,
401
- })
399
+ type NetworkService interface {
400
+ StartService (t * testing.T ,logger slog.Logger ,conn * tailnet.Conn )
402
401
}
403
402
404
- // StartClientDirect does the same thing as StartClientDERP but disables
405
- // BlockEndpoints (which enables Direct connections), and waits for a direct
406
- // connection to be established between the two peers.
407
- func StartClientDirect (t * testing.T ,logger slog.Logger ,serverURL * url.URL ,derpMap * tailcfg.DERPMap ,me ,peer Client )* tailnet.Conn {
403
+ func (b BasicClientStarter )StartClient (t * testing.T ,logger slog.Logger ,serverURL * url.URL ,derpMap * tailcfg.DERPMap ,me ,peer Client )* tailnet.Conn {
404
+ var hook capture.Callback
405
+ if b .LogPackets {
406
+ pktLogger := packetLogger {logger }
407
+ hook = pktLogger .LogPacket
408
+ }
408
409
conn := startClientOptions (t ,logger ,serverURL ,me ,peer ,& tailnet.Options {
409
410
Addresses : []netip.Prefix {tailnet .TailscaleServicePrefix .PrefixFromUUID (me .ID )},
410
411
DERPMap :derpMap ,
411
- BlockEndpoints :false ,
412
+ BlockEndpoints :b . BlockEndpoints ,
412
413
Logger :logger ,
413
- DERPForceWebSockets :true ,
414
+ DERPForceWebSockets :b . DERPForceWebsockets ,
414
415
ListenPort :me .ListenPort ,
415
416
// These tests don't have internet connection, so we need to force
416
417
// magicsock to do anything.
417
418
ForceNetworkUp :true ,
419
+ CaptureHook :hook ,
418
420
})
419
421
420
- // Wait for direct connection to be established.
421
- peerIP := tailnet .TailscaleServicePrefix .AddrFromUUID (peer .ID )
422
- require .Eventually (t ,func ()bool {
423
- t .Log ("attempting ping to peer to judge direct connection" )
424
- ctx := testutil .Context (t ,testutil .WaitShort )
425
- _ ,p2p ,pong ,err := conn .Ping (ctx ,peerIP )
426
- if err != nil {
427
- t .Logf ("ping failed: %v" ,err )
428
- return false
429
- }
430
- if ! p2p {
431
- t .Log ("ping succeeded, but not direct yet" )
432
- return false
433
- }
434
- t .Logf ("ping succeeded, direct connection established via %s" ,pong .Endpoint )
435
- return true
436
- },testutil .WaitLong ,testutil .IntervalMedium )
422
+ if b .Service != nil {
423
+ b .Service .StartService (t ,logger ,conn )
424
+ }
425
+
426
+ if b .WaitForConnection || b .WaitForDirect {
427
+ // Wait for connection to be established.
428
+ peerIP := tailnet .TailscaleServicePrefix .AddrFromUUID (peer .ID )
429
+ require .Eventually (t ,func ()bool {
430
+ t .Log ("attempting ping to peer to judge direct connection" )
431
+ ctx := testutil .Context (t ,testutil .WaitShort )
432
+ _ ,p2p ,pong ,err := conn .Ping (ctx ,peerIP )
433
+ if err != nil {
434
+ t .Logf ("ping failed: %v" ,err )
435
+ return false
436
+ }
437
+ if ! p2p && b .WaitForDirect {
438
+ t .Log ("ping succeeded, but not direct yet" )
439
+ return false
440
+ }
441
+ t .Logf ("ping succeeded, p2p=%t, endpoint=%s" ,p2p ,pong .Endpoint )
442
+ return true
443
+ },testutil .WaitLong ,testutil .IntervalMedium )
444
+ }
437
445
438
446
return conn
439
447
}
440
448
441
- type ClientStarter struct {
442
- Options * tailnet.Options
449
+ const EchoPort = 2381
450
+
451
+ type UDPEchoService struct {}
452
+
453
+ func (UDPEchoService )StartService (t * testing.T ,logger slog.Logger ,_ * tailnet.Conn ) {
454
+ // tailnet doesn't handle UDP connections "in-process" the way we do for TCP, so we need to listen in the OS,
455
+ // and tailnet will forward packets.
456
+ l ,err := net .ListenUDP ("udp" ,& net.UDPAddr {
457
+ IP :net .IPv6zero ,// all interfaces
458
+ Port :EchoPort ,
459
+ })
460
+ require .NoError (t ,err )
461
+ logger .Info (context .Background (),"started UDPEcho server" )
462
+ t .Cleanup (func () {
463
+ lCloseErr := l .Close ()
464
+ if lCloseErr != nil {
465
+ t .Logf ("error closing UDPEcho listener: %v" ,lCloseErr )
466
+ }
467
+ })
468
+ go func () {
469
+ buf := make ([]byte ,1500 )
470
+ for {
471
+ n ,remote ,readErr := l .ReadFromUDP (buf )
472
+ if readErr != nil {
473
+ logger .Info (context .Background (),"error reading UDPEcho listener" ,slog .Error (readErr ))
474
+ return
475
+ }
476
+ logger .Info (context .Background (),"received UDPEcho packet" ,
477
+ slog .F ("len" ,n ),slog .F ("remote" ,remote ))
478
+ n ,writeErr := l .WriteToUDP (buf [:n ],remote )
479
+ if writeErr != nil {
480
+ logger .Info (context .Background (),"error writing UDPEcho listener" ,slog .Error (writeErr ))
481
+ return
482
+ }
483
+ logger .Info (context .Background (),"wrote UDPEcho packet" ,
484
+ slog .F ("len" ,n ),slog .F ("remote" ,remote ))
485
+ }
486
+ }()
443
487
}
444
488
445
489
func startClientOptions (t * testing.T ,logger slog.Logger ,serverURL * url.URL ,me ,peer Client ,options * tailnet.Options )* tailnet.Conn {
@@ -467,9 +511,16 @@ func startClientOptions(t *testing.T, logger slog.Logger, serverURL *url.URL, me
467
511
_ = conn .Close ()
468
512
})
469
513
470
- ctrl := tailnet .NewTunnelSrcCoordController (logger ,conn )
471
- ctrl .AddDestination (peer .ID )
472
- coordination := ctrl .New (coord )
514
+ var coordination tailnet.CloserWaiter
515
+ if me .TunnelSrc {
516
+ ctrl := tailnet .NewTunnelSrcCoordController (logger ,conn )
517
+ ctrl .AddDestination (peer .ID )
518
+ coordination = ctrl .New (coord )
519
+ }else {
520
+ // use the "Agent" controller so that we act as a tunnel destination and send "ReadyForHandshake" acks.
521
+ ctrl := tailnet .NewAgentCoordinationController (logger ,conn )
522
+ coordination = ctrl .New (coord )
523
+ }
473
524
t .Cleanup (func () {
474
525
cctx ,cancel := context .WithTimeout (context .Background (),testutil .WaitShort )
475
526
defer cancel ()
@@ -492,11 +543,17 @@ func basicDERPMap(serverURLStr string) (*tailcfg.DERPMap, error) {
492
543
}
493
544
494
545
hostname := serverURL .Hostname ()
495
- ipv4 := ""
546
+ ipv4 := "none"
547
+ ipv6 := "none"
496
548
ip ,err := netip .ParseAddr (hostname )
497
549
if err == nil {
498
550
hostname = ""
499
- ipv4 = ip .String ()
551
+ if ip .Is4 () {
552
+ ipv4 = ip .String ()
553
+ }
554
+ if ip .Is6 () {
555
+ ipv6 = ip .String ()
556
+ }
500
557
}
501
558
502
559
return & tailcfg.DERPMap {
@@ -511,7 +568,7 @@ func basicDERPMap(serverURLStr string) (*tailcfg.DERPMap, error) {
511
568
RegionID :1 ,
512
569
HostName :hostname ,
513
570
IPv4 :ipv4 ,
514
- IPv6 :"none" ,
571
+ IPv6 :ipv6 ,
515
572
DERPPort :port ,
516
573
STUNPort :- 1 ,
517
574
ForceHTTP :true ,
@@ -648,3 +705,35 @@ func (w *testWriter) Flush() {
648
705
}
649
706
w .capturedLines = nil
650
707
}
708
+
709
+ type packetLogger struct {
710
+ l slog.Logger
711
+ }
712
+
713
+ func (p packetLogger )LogPacket (path capture.Path ,when time.Time ,pkt []byte ,_ packet.CaptureMeta ) {
714
+ q := new (packet.Parsed )
715
+ q .Decode (pkt )
716
+ p .l .Info (context .Background (),"Packet" ,
717
+ slog .F ("path" ,pathString (path )),
718
+ slog .F ("when" ,when ),
719
+ slog .F ("decode" ,q .String ()),
720
+ slog .F ("len" ,len (pkt )),
721
+ )
722
+ }
723
+
724
+ func pathString (path capture.Path )string {
725
+ switch path {
726
+ case capture .FromLocal :
727
+ return "Local"
728
+ case capture .FromPeer :
729
+ return "Peer"
730
+ case capture .SynthesizedToLocal :
731
+ return "SynthesizedToLocal"
732
+ case capture .SynthesizedToPeer :
733
+ return "SynthesizedToPeer"
734
+ case capture .PathDisco :
735
+ return "Disco"
736
+ default :
737
+ return "<<UNKNOWN>>"
738
+ }
739
+ }