Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit08eff7f

Browse files
authored
chore: improve tailnet integration test (#18124)
Refactors tailnet integration test and adds UDP echo tests with different MTU related to#15523I still haven't gotten to the bottom of what's causing the issue (the added test case I expected to fail actually succeeds), but these integration test improvements are generally useful.also: * consolidates networking setup with easy and hard NAT * consolidates client setup * makes Client2 act like an agent at the tailnet layer, so it will send ReadyForHandshake and speed up the tunnel establishment * adds support for logging tunneled packets * adds support for dumping outer (underlay) IP traffic * adds support for adjusting veth MTU * adds support for IPv6 in the outer (underlay) network topology
1 parent628b81c commit08eff7f

File tree

4 files changed

+464
-196
lines changed

4 files changed

+464
-196
lines changed

‎tailnet/test/integration/integration.go

Lines changed: 154 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ import (
2828
"golang.org/x/xerrors"
2929
"tailscale.com/derp"
3030
"tailscale.com/derp/derphttp"
31+
"tailscale.com/net/packet"
3132
"tailscale.com/tailcfg"
3233
"tailscale.com/types/key"
34+
"tailscale.com/wgengine/capture"
3335

3436
"cdr.dev/slog"
3537
"github.com/coder/coder/v2/coderd/httpapi"
@@ -54,35 +56,36 @@ type Client struct {
5456
ID uuid.UUID
5557
ListenPortuint16
5658
ShouldRunTestsbool
59+
TunnelSrcbool
5760
}
5861

5962
varClient1=Client{
6063
Number:ClientNumber1,
6164
ID:uuid.MustParse("00000000-0000-0000-0000-000000000001"),
6265
ListenPort:client1Port,
6366
ShouldRunTests:true,
67+
TunnelSrc:true,
6468
}
6569

6670
varClient2=Client{
6771
Number:ClientNumber2,
6872
ID:uuid.MustParse("00000000-0000-0000-0000-000000000002"),
6973
ListenPort:client2Port,
7074
ShouldRunTests:false,
75+
TunnelSrc:false,
7176
}
7277

7378
typeTestTopologystruct {
7479
Namestring
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-
SetupNetworkingfunc(t*testing.T,logger slog.Logger)TestNetworking
80+
81+
NetworkingProviderNetworkingProvider
7982

8083
// Server is the server starter for the test. It is executed in the server
8184
// subprocess.
8285
ServerServerStarter
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
8487
// create the tailnet.Conn and ensure connectivity to it's peer.
85-
StartClientfunc(t*testing.T,logger slog.Logger,serverURL*url.URL,derpMap*tailcfg.DERPMap,meClient,peerClient)*tailnet.Conn
88+
ClientStarterClientStarter
8689

8790
// RunTests is the main test function. It's called in each of the client
8891
// subprocesses. If tests can only run once, they should check the client ID
@@ -97,6 +100,17 @@ type ServerStarter interface {
97100
StartServer(t*testing.T,logger slog.Logger,listenAddrstring)
98101
}
99102

103+
typeNetworkingProviderinterface {
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+
typeClientStarterinterface {
111+
StartClient(t*testing.T,logger slog.Logger,serverURL*url.URL,derpMap*tailcfg.DERPMap,meClient,peerClient)*tailnet.Conn
112+
}
113+
100114
typeSimpleServerOptionsstruct {
101115
// FailUpgradeDERP will make the DERP server fail to handle the initial DERP
102116
// upgrade in a way that causes the client to fallback to
@@ -369,77 +383,107 @@ http {
369383
_,_=ExecBackground(t,"server.nginx",nil,"nginx", []string{"-c",cfgPath})
370384
}
371385

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-
funcStartClientDERP(t*testing.T,logger slog.Logger,serverURL*url.URL,derpMap*tailcfg.DERPMap,me,peerClient)*tailnet.Conn {
375-
returnstartClientOptions(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+
typeBasicClientStarterstruct {
387+
BlockEndpointsbool
388+
DERPForceWebsocketsbool
389+
// WaitForConnection means wait for (any) peer connection before returning from StartClient
390+
WaitForConnectionbool
391+
// WaitForConnection means wait for a direct peer connection before returning from StartClient
392+
WaitForDirectbool
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+
ServiceNetworkService
396+
LogPacketsbool
386397
}
387398

388-
// StartClientDERPWebSockets does the same thing as StartClientDERP but will
389-
// only use DERP WebSocket fallback.
390-
funcStartClientDERPWebSockets(t*testing.T,logger slog.Logger,serverURL*url.URL,derpMap*tailcfg.DERPMap,me,peerClient)*tailnet.Conn {
391-
returnstartClientOptions(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+
typeNetworkServiceinterface {
400+
StartService(t*testing.T,logger slog.Logger,conn*tailnet.Conn)
402401
}
403402

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-
funcStartClientDirect(t*testing.T,logger slog.Logger,serverURL*url.URL,derpMap*tailcfg.DERPMap,me,peerClient)*tailnet.Conn {
403+
func (bBasicClientStarter)StartClient(t*testing.T,logger slog.Logger,serverURL*url.URL,derpMap*tailcfg.DERPMap,me,peerClient)*tailnet.Conn {
404+
varhook capture.Callback
405+
ifb.LogPackets {
406+
pktLogger:=packetLogger{logger}
407+
hook=pktLogger.LogPacket
408+
}
408409
conn:=startClientOptions(t,logger,serverURL,me,peer,&tailnet.Options{
409410
Addresses: []netip.Prefix{tailnet.TailscaleServicePrefix.PrefixFromUUID(me.ID)},
410411
DERPMap:derpMap,
411-
BlockEndpoints:false,
412+
BlockEndpoints:b.BlockEndpoints,
412413
Logger:logger,
413-
DERPForceWebSockets:true,
414+
DERPForceWebSockets:b.DERPForceWebsockets,
414415
ListenPort:me.ListenPort,
415416
// These tests don't have internet connection, so we need to force
416417
// magicsock to do anything.
417418
ForceNetworkUp:true,
419+
CaptureHook:hook,
418420
})
419421

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-
iferr!=nil {
427-
t.Logf("ping failed: %v",err)
428-
returnfalse
429-
}
430-
if!p2p {
431-
t.Log("ping succeeded, but not direct yet")
432-
returnfalse
433-
}
434-
t.Logf("ping succeeded, direct connection established via %s",pong.Endpoint)
435-
returntrue
436-
},testutil.WaitLong,testutil.IntervalMedium)
422+
ifb.Service!=nil {
423+
b.Service.StartService(t,logger,conn)
424+
}
425+
426+
ifb.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+
iferr!=nil {
434+
t.Logf("ping failed: %v",err)
435+
returnfalse
436+
}
437+
if!p2p&&b.WaitForDirect {
438+
t.Log("ping succeeded, but not direct yet")
439+
returnfalse
440+
}
441+
t.Logf("ping succeeded, p2p=%t, endpoint=%s",p2p,pong.Endpoint)
442+
returntrue
443+
},testutil.WaitLong,testutil.IntervalMedium)
444+
}
437445

438446
returnconn
439447
}
440448

441-
typeClientStarterstruct {
442-
Options*tailnet.Options
449+
constEchoPort=2381
450+
451+
typeUDPEchoServicestruct{}
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+
iflCloseErr!=nil {
465+
t.Logf("error closing UDPEcho listener: %v",lCloseErr)
466+
}
467+
})
468+
gofunc() {
469+
buf:=make([]byte,1500)
470+
for {
471+
n,remote,readErr:=l.ReadFromUDP(buf)
472+
ifreadErr!=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+
ifwriteErr!=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+
}()
443487
}
444488

445489
funcstartClientOptions(t*testing.T,logger slog.Logger,serverURL*url.URL,me,peerClient,options*tailnet.Options)*tailnet.Conn {
@@ -467,9 +511,16 @@ func startClientOptions(t *testing.T, logger slog.Logger, serverURL *url.URL, me
467511
_=conn.Close()
468512
})
469513

470-
ctrl:=tailnet.NewTunnelSrcCoordController(logger,conn)
471-
ctrl.AddDestination(peer.ID)
472-
coordination:=ctrl.New(coord)
514+
varcoordination tailnet.CloserWaiter
515+
ifme.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+
}
473524
t.Cleanup(func() {
474525
cctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
475526
defercancel()
@@ -492,11 +543,17 @@ func basicDERPMap(serverURLStr string) (*tailcfg.DERPMap, error) {
492543
}
493544

494545
hostname:=serverURL.Hostname()
495-
ipv4:=""
546+
ipv4:="none"
547+
ipv6:="none"
496548
ip,err:=netip.ParseAddr(hostname)
497549
iferr==nil {
498550
hostname=""
499-
ipv4=ip.String()
551+
ifip.Is4() {
552+
ipv4=ip.String()
553+
}
554+
ifip.Is6() {
555+
ipv6=ip.String()
556+
}
500557
}
501558

502559
return&tailcfg.DERPMap{
@@ -511,7 +568,7 @@ func basicDERPMap(serverURLStr string) (*tailcfg.DERPMap, error) {
511568
RegionID:1,
512569
HostName:hostname,
513570
IPv4:ipv4,
514-
IPv6:"none",
571+
IPv6:ipv6,
515572
DERPPort:port,
516573
STUNPort:-1,
517574
ForceHTTP:true,
@@ -648,3 +705,35 @@ func (w *testWriter) Flush() {
648705
}
649706
w.capturedLines=nil
650707
}
708+
709+
typepacketLoggerstruct {
710+
l slog.Logger
711+
}
712+
713+
func (ppacketLogger)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+
funcpathString(path capture.Path)string {
725+
switchpath {
726+
casecapture.FromLocal:
727+
return"Local"
728+
casecapture.FromPeer:
729+
return"Peer"
730+
casecapture.SynthesizedToLocal:
731+
return"SynthesizedToLocal"
732+
casecapture.SynthesizedToPeer:
733+
return"SynthesizedToPeer"
734+
casecapture.PathDisco:
735+
return"Disco"
736+
default:
737+
return"<<UNKNOWN>>"
738+
}
739+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp