Files
coder/coderd/tailnet_internal_test.go
T
Ethan 939d05b906 fix(coderd): never dial the embedded DERP region over the network (#30221)
Fixes CLI-9

The `goleak` check in `cli` has been flaking on an idle HTTP connection.
It turns out that's coming from `coder server` shutting down, not the
OIDC links command.

`API.Close` cancels the API context well before it closes the server
tailnet, and since #14680 the embedded DERP region dialer returns `nil`
once that context is done. Unfortunately, `nil` tells `derphttp` to dial
the region over the network instead. So the server dials its own access
URL (`example.com` in tests), gets a 404, falls back to WebSocket, and
our tailscale fork leaves that failed WebSocket connection open forever.

This has only been flaking since #29462, which has the server tailnet
fetch the DERP map straight away rather than after the first 5 second
tick. Before that, most server tests had finished before the tailnet
ever connected to DERP, so there was nothing to reconnect on shutdown.

To fix, the embedded region now gets an already closed conn once
shutdown starts, so the DERP handshake fails and magicsock just backs
off until the tailnet closes. I reproduced the leak by pointing a server
tailnet's embedded region at `example.com` and cancelling the context;
it fails `goleak` without this change and passes with it.
2026-10-05 13:28:26 +11:00

54 lines
1.5 KiB
Go

package coderd
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"tailscale.com/derp"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"github.com/coder/coder/v2/testutil"
"github.com/coder/quartz"
)
func TestEmbeddedDERPRegionDialer(t *testing.T) {
t.Parallel()
srv := derp.NewServer(key.NewNode(), func(format string, args ...any) { t.Logf(format, args...) })
t.Cleanup(func() { _ = srv.Close() })
ctx, cancel := context.WithCancel(testutil.Context(t, testutil.WaitShort))
dial := embeddedDERPRegionDialer(ctx, testutil.Logger(t), srv)
require.Nil(t, dial(ctx, &tailcfg.DERPRegion{EmbeddedRelay: false}))
cancel()
conn := dial(ctx, &tailcfg.DERPRegion{EmbeddedRelay: true})
require.NotNil(t, conn, "an embedded region must never fall back to a network dial")
_, err := conn.Read(make([]byte, 1))
require.Error(t, err)
}
func TestPollingDERPClient_FirstRecvDoesNotWaitForTick(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitShort)
derpMap := &tailcfg.DERPMap{Regions: map[int]*tailcfg.DERPRegion{1: {RegionID: 1}}}
// The mock clock never advances, so a poll loop that waits for the
// first tick would block forever.
client := newPollingDERPClient(func() *tailcfg.DERPMap { return derpMap }, testutil.Logger(t), quartz.NewMock(t))
defer client.Close()
got := make(chan *tailcfg.DERPMap, 1)
go func() {
dm, err := client.Recv()
if err != nil {
t.Error("recv derp map:", err)
return
}
got <- dm
}()
require.Equal(t, derpMap, testutil.RequireReceive(ctx, t, got))
}