Skip to content

Commit 264fc56

Browse files
committed
chore: add easy NAT integration tests part 2
1 parent a63d427 commit 264fc56

File tree

5 files changed

+592
-194
lines changed

5 files changed

+592
-194
lines changed

enterprise/coderd/roles_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestCustomRole(t *testing.T) {
6464

6565
// Verify the role exists in the list
6666
// TODO: Turn this assertion back on when the cli api experience is created.
67-
//allRoles, err := tmplAdmin.ListSiteRoles(ctx)
67+
// allRoles, err := tmplAdmin.ListSiteRoles(ctx)
6868
//require.NoError(t, err)
6969
//
7070
//require.True(t, slices.ContainsFunc(allRoles, func(selected codersdk.AssignableRoles) bool {

tailnet/test/integration/integration.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ type TestTopology struct {
5959
Server ServerStarter
6060
// StartClient gets called in each client subprocess. It's expected to
6161
// create the tailnet.Conn and ensure connectivity to it's peer.
62-
StartClient func(t *testing.T, logger slog.Logger, serverURL *url.URL, myID uuid.UUID, peerID uuid.UUID) *tailnet.Conn
62+
StartClient func(t *testing.T, logger slog.Logger, serverURL *url.URL, derpMap *tailcfg.DERPMap, clientNumber int, myID uuid.UUID, peerID uuid.UUID) *tailnet.Conn
6363

6464
// RunTests is the main test function. It's called in each of the client
6565
// subprocesses. If tests can only run once, they should check the client ID
@@ -264,13 +264,18 @@ http {
264264

265265
// StartClientDERP creates a client connection to the server for coordination
266266
// and creates a tailnet.Conn which will only use DERP to connect to the peer.
267-
func StartClientDERP(t *testing.T, logger slog.Logger, serverURL *url.URL, myID, peerID uuid.UUID) *tailnet.Conn {
267+
func StartClientDERP(t *testing.T, logger slog.Logger, serverURL *url.URL, derpMap *tailcfg.DERPMap, clientNumber int, myID, peerID uuid.UUID) *tailnet.Conn {
268+
listenPort := uint16(client1Port)
269+
if clientNumber == 2 {
270+
listenPort = client2Port
271+
}
268272
return startClientOptions(t, logger, serverURL, myID, peerID, &tailnet.Options{
269273
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IPFromUUID(myID), 128)},
270-
DERPMap: basicDERPMap(t, serverURL),
274+
DERPMap: derpMap,
271275
BlockEndpoints: true,
272276
Logger: logger,
273277
DERPForceWebSockets: false,
278+
ListenPort: listenPort,
274279
// These tests don't have internet connection, so we need to force
275280
// magicsock to do anything.
276281
ForceNetworkUp: true,
@@ -279,13 +284,18 @@ func StartClientDERP(t *testing.T, logger slog.Logger, serverURL *url.URL, myID,
279284

280285
// StartClientDERPWebSockets does the same thing as StartClientDERP but will
281286
// only use DERP WebSocket fallback.
282-
func StartClientDERPWebSockets(t *testing.T, logger slog.Logger, serverURL *url.URL, myID, peerID uuid.UUID) *tailnet.Conn {
287+
func StartClientDERPWebSockets(t *testing.T, logger slog.Logger, serverURL *url.URL, derpMap *tailcfg.DERPMap, clientNumber int, myID, peerID uuid.UUID) *tailnet.Conn {
288+
listenPort := uint16(client1Port)
289+
if clientNumber == 2 {
290+
listenPort = client2Port
291+
}
283292
return startClientOptions(t, logger, serverURL, myID, peerID, &tailnet.Options{
284293
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IPFromUUID(myID), 128)},
285-
DERPMap: basicDERPMap(t, serverURL),
294+
DERPMap: derpMap,
286295
BlockEndpoints: true,
287296
Logger: logger,
288297
DERPForceWebSockets: true,
298+
ListenPort: listenPort,
289299
// These tests don't have internet connection, so we need to force
290300
// magicsock to do anything.
291301
ForceNetworkUp: true,
@@ -295,13 +305,18 @@ func StartClientDERPWebSockets(t *testing.T, logger slog.Logger, serverURL *url.
295305
// StartClientDirect does the same thing as StartClientDERP but disables
296306
// BlockEndpoints (which enables Direct connections), and waits for a direct
297307
// connection to be established between the two peers.
298-
func StartClientDirect(t *testing.T, logger slog.Logger, serverURL *url.URL, myID, peerID uuid.UUID) *tailnet.Conn {
308+
func StartClientDirect(t *testing.T, logger slog.Logger, serverURL *url.URL, derpMap *tailcfg.DERPMap, clientNumber int, myID, peerID uuid.UUID) *tailnet.Conn {
309+
listenPort := uint16(client1Port)
310+
if clientNumber == 2 {
311+
listenPort = client2Port
312+
}
299313
conn := startClientOptions(t, logger, serverURL, myID, peerID, &tailnet.Options{
300314
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IPFromUUID(myID), 128)},
301-
DERPMap: basicDERPMap(t, serverURL),
315+
DERPMap: derpMap,
302316
BlockEndpoints: false,
303317
Logger: logger,
304318
DERPForceWebSockets: true,
319+
ListenPort: listenPort,
305320
// These tests don't have internet connection, so we need to force
306321
// magicsock to do anything.
307322
ForceNetworkUp: true,
@@ -365,10 +380,17 @@ func startClientOptions(t *testing.T, logger slog.Logger, serverURL *url.URL, my
365380
return conn
366381
}
367382

368-
func basicDERPMap(t *testing.T, serverURL *url.URL) *tailcfg.DERPMap {
383+
func basicDERPMap(serverURLStr string) (*tailcfg.DERPMap, error) {
384+
serverURL, err := url.Parse(serverURLStr)
385+
if err != nil {
386+
return nil, xerrors.Errorf("parse server URL %q: %w", serverURLStr, err)
387+
}
388+
369389
portStr := serverURL.Port()
370390
port, err := strconv.Atoi(portStr)
371-
require.NoError(t, err, "parse server port")
391+
if err != nil {
392+
return nil, xerrors.Errorf("parse port %q: %w", portStr, err)
393+
}
372394

373395
hostname := serverURL.Hostname()
374396
ipv4 := ""
@@ -399,7 +421,7 @@ func basicDERPMap(t *testing.T, serverURL *url.URL) *tailcfg.DERPMap {
399421
},
400422
},
401423
},
402-
}
424+
}, nil
403425
}
404426

405427
// ExecBackground starts a subprocess with the given flags and returns a

tailnet/test/integration/integration_test.go

Lines changed: 123 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,28 @@
44
package integration_test
55

66
import (
7+
"context"
8+
"encoding/json"
79
"flag"
810
"fmt"
11+
"net"
912
"net/http"
1013
"net/url"
1114
"os"
1215
"os/signal"
16+
"path/filepath"
1317
"runtime"
18+
"strconv"
1419
"syscall"
1520
"testing"
1621
"time"
1722

1823
"github.com/google/uuid"
24+
"github.com/stretchr/testify/assert"
1925
"github.com/stretchr/testify/require"
26+
"tailscale.com/net/stun/stuntest"
27+
"tailscale.com/tailcfg"
28+
"tailscale.com/types/nettype"
2029

2130
"cdr.dev/slog"
2231
"cdr.dev/slog/sloggers/slogtest"
@@ -30,17 +39,22 @@ const runTestEnv = "CODER_TAILNET_TESTS"
3039
var (
3140
isSubprocess = flag.Bool("subprocess", false, "Signifies that this is a test subprocess")
3241
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")
3443

3544
// Role: server
3645
serverListenAddr = flag.String("server-listen-addr", "", "The address to listen on for the server")
3746

47+
// Role: stun
48+
stunListenAddr = flag.String("stun-listen-addr", "", "The address to listen on for the STUN server")
49+
3850
// 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")
4458
)
4559

4660
func TestMain(m *testing.M) {
@@ -87,7 +101,7 @@ var topologies = []integration.TestTopology{
87101
// endpoints to connect as routing is enabled between client 1 and
88102
// client 2.
89103
Name: "EasyNATDirect",
90-
SetupNetworking: integration.SetupNetworkingEasyNAT,
104+
SetupNetworking: integration.SetupNetworkingEasyNATWithSTUN,
91105
Server: integration.SimpleServerOptions{},
92106
StartClient: integration.StartClientDirect,
93107
RunTests: integration.TestSuite,
@@ -143,17 +157,41 @@ func TestIntegration(t *testing.T) {
143157
log := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
144158
networking := topo.SetupNetworking(t, log)
145159

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+
147165
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+
148185
// 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)
151188

152189
// Wait for client1 to exit.
153190
require.NoError(t, <-client1ErrCh, "client 1 exited")
154191

155192
// Close client2 and the server.
156193
require.NoError(t, closeClient2(), "client 2 exited")
194+
require.NoError(t, closeSTUN(), "stun exited")
157195
require.NoError(t, closeServer(), "server exited")
158196
})
159197
}
@@ -169,10 +207,11 @@ func handleTestSubprocess(t *testing.T) {
169207
}
170208
}
171209
require.NotEmptyf(t, topo.Name, "unknown test topology %q", *testID)
210+
require.Contains(t, []string{"server", "stun", "client"}, *role, "unknown role %q", *role)
172211

173212
testName := topo.Name + "/"
174-
if *role == "server" {
175-
testName += "server"
213+
if *role == "server" || *role == "stun" {
214+
testName += *role
176215
} else {
177216
testName += *clientName
178217
}
@@ -185,18 +224,34 @@ func handleTestSubprocess(t *testing.T) {
185224
topo.Server.StartServer(t, logger, *serverListenAddr)
186225
// no exit
187226

227+
case "stun":
228+
launchSTUNServer(t, *stunListenAddr)
229+
// no exit
230+
188231
case "client":
189232
logger = logger.Named(*clientName)
233+
if *clientNumber != 1 && *clientNumber != 2 {
234+
t.Fatalf("invalid client number %d", clientNumber)
235+
}
190236
serverURL, err := url.Parse(*clientServerURL)
191237
require.NoErrorf(t, err, "parse server url %q", *clientServerURL)
192238
myID, err := uuid.Parse(*clientMyID)
193239
require.NoErrorf(t, err, "parse client id %q", *clientMyID)
194240
peerID, err := uuid.Parse(*clientPeerID)
195241
require.NoErrorf(t, err, "parse peer id %q", *clientPeerID)
196242

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+
197252
waitForServerAvailable(t, serverURL)
198253

199-
conn := topo.StartClient(t, logger, serverURL, myID, peerID)
254+
conn := topo.StartClient(t, logger, serverURL, &derpMap, *clientNumber, myID, peerID)
200255

201256
if *clientRunTests {
202257
// Wait for connectivity.
@@ -218,6 +273,23 @@ func handleTestSubprocess(t *testing.T) {
218273
})
219274
}
220275

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+
221293
func waitForServerAvailable(t *testing.T, serverURL *url.URL) {
222294
const delay = 100 * time.Millisecond
223295
const reqTimeout = 2 * time.Second
@@ -247,45 +319,55 @@ func waitForServerAvailable(t *testing.T, serverURL *url.URL) {
247319
}
248320

249321
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{
251323
"--subprocess",
252324
"--test-name=" + topologyName,
253325
"--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,
255337
})
256338
return closeFn
257339
}
258340

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) {
260342
require.True(t, clientNumber == 1 || clientNumber == 2)
261343

262344
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
268349
)
269350
if clientNumber == 2 {
270351
myID, peerID = peerID, myID
271-
accessURL = networking.ServerAccessURLClient2
272-
netNS = networking.ProcessClient2.NetNS
352+
clientConfig = networking.Client2
273353
}
274354

275355
flags := []string{
276356
"--subprocess",
277357
"--test-name=" + topologyName,
278358
"--role=client",
279359
"--client-name=" + clientName,
280-
"--client-server-url=" + accessURL,
360+
"--client-number=" + strconv.Itoa(clientNumber),
361+
"--client-server-url=" + clientConfig.ServerAccessURL,
281362
"--client-id=" + myID.String(),
282363
"--client-peer-id=" + peerID.String(),
364+
"--client-derp-map-path=" + derpMapPath,
283365
}
284366
if clientNumber == 1 {
285367
flags = append(flags, "--client-run-tests")
286368
}
287369

288-
return startSubprocess(t, clientName, netNS, flags)
370+
return startSubprocess(t, clientName, clientConfig.Process.NetNS, flags)
289371
}
290372

291373
// 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
298380
args := append(os.Args[1:], append([]string{"-test.v=true"}, flags...)...)
299381
return integration.ExecBackground(t, processName, netNS, name, args)
300382
}
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

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy