Skip to content

Commit 2d0b910

Browse files
authored
fix: change servertailnet to register the DERP dialer before setting DERP map (coder#12137)
I noticed a possible race where tailnet.Conn can try to dial the embedded region before we've set our custom dialer that send the DERP in-memory. This closes that race and adds a test case for servertailnet with no STUN and an embedded relay
1 parent 1bb4aec commit 2d0b910

File tree

5 files changed

+81
-34
lines changed

5 files changed

+81
-34
lines changed

coderd/tailnet.go

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -52,19 +52,41 @@ func NewServerTailnet(
5252
traceProvider trace.TracerProvider,
5353
) (*ServerTailnet, error) {
5454
logger = logger.Named("servertailnet")
55-
originalDerpMap := derpMapFn()
5655
conn, err := tailnet.NewConn(&tailnet.Options{
5756
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
58-
DERPMap: originalDerpMap,
5957
DERPForceWebSockets: derpForceWebSockets,
6058
Logger: logger,
6159
})
6260
if err != nil {
6361
return nil, xerrors.Errorf("create tailnet conn: %w", err)
6462
}
65-
6663
serverCtx, cancel := context.WithCancel(ctx)
64+
65+
// This is set to allow local DERP traffic to be proxied through memory
66+
// instead of needing to hit the external access URL. Don't use the ctx
67+
// given in this callback, it's only valid while connecting.
68+
if derpServer != nil {
69+
conn.SetDERPRegionDialer(func(_ context.Context, region *tailcfg.DERPRegion) net.Conn {
70+
if !region.EmbeddedRelay {
71+
return nil
72+
}
73+
logger.Debug(ctx, "connecting to embedded DERP via in-memory pipe")
74+
left, right := net.Pipe()
75+
go func() {
76+
defer left.Close()
77+
defer right.Close()
78+
brw := bufio.NewReadWriter(bufio.NewReader(right), bufio.NewWriter(right))
79+
derpServer.Accept(ctx, right, brw, "internal")
80+
}()
81+
return left
82+
})
83+
}
84+
6785
derpMapUpdaterClosed := make(chan struct{})
86+
originalDerpMap := derpMapFn()
87+
// it's important to set the DERPRegionDialer above _before_ we set the DERP map so that if
88+
// there is an embedded relay, we use the local in-memory dialer.
89+
conn.SetDERPMap(originalDerpMap)
6890
go func() {
6991
defer close(derpMapUpdaterClosed)
7092

@@ -139,25 +161,6 @@ func NewServerTailnet(
139161
// registering the callback also triggers send of the initial node
140162
tn.coordinatee.SetNodeCallback(tn.nodeCallback)
141163

142-
// This is set to allow local DERP traffic to be proxied through memory
143-
// instead of needing to hit the external access URL. Don't use the ctx
144-
// given in this callback, it's only valid while connecting.
145-
if derpServer != nil {
146-
conn.SetDERPRegionDialer(func(_ context.Context, region *tailcfg.DERPRegion) net.Conn {
147-
if !region.EmbeddedRelay {
148-
return nil
149-
}
150-
left, right := net.Pipe()
151-
go func() {
152-
defer left.Close()
153-
defer right.Close()
154-
brw := bufio.NewReadWriter(bufio.NewReader(right), bufio.NewWriter(right))
155-
derpServer.Accept(ctx, right, brw, "internal")
156-
}()
157-
return left
158-
})
159-
}
160-
161164
go tn.watchAgentUpdates()
162165
go tn.expireOldAgents()
163166
return tn, nil

coderd/tailnet_test.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,24 @@ func TestServerTailnet_AgentConn_OK(t *testing.T) {
5050
assert.True(t, conn.AwaitReachable(ctx))
5151
}
5252

53+
func TestServerTailnet_AgentConn_NoSTUN(t *testing.T) {
54+
t.Parallel()
55+
56+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
57+
defer cancel()
58+
59+
// Connect through the ServerTailnet
60+
agents, serverTailnet := setupServerTailnetAgent(t, 1,
61+
tailnettest.DisableSTUN, tailnettest.DERPIsEmbedded)
62+
a := agents[0]
63+
64+
conn, release, err := serverTailnet.AgentConn(ctx, a.id)
65+
require.NoError(t, err)
66+
defer release()
67+
68+
assert.True(t, conn.AwaitReachable(ctx))
69+
}
70+
5371
func TestServerTailnet_ReverseProxy(t *testing.T) {
5472
t.Parallel()
5573

@@ -311,9 +329,9 @@ type agentWithID struct {
311329
agent.Agent
312330
}
313331

314-
func setupServerTailnetAgent(t *testing.T, agentNum int) ([]agentWithID, *coderd.ServerTailnet) {
332+
func setupServerTailnetAgent(t *testing.T, agentNum int, opts ...tailnettest.DERPAndStunOption) ([]agentWithID, *coderd.ServerTailnet) {
315333
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
316-
derpMap, derpServer := tailnettest.RunDERPAndSTUN(t)
334+
derpMap, derpServer := tailnettest.RunDERPAndSTUN(t, opts...)
317335

318336
coord := tailnet.NewCoordinator(logger)
319337
t.Cleanup(func() {

tailnet/configmaps.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ func (c *configMaps) netMapLocked() *netmap.NetworkMap {
204204
nm.Addresses = make([]netip.Prefix, len(c.addresses))
205205
copy(nm.Addresses, c.addresses)
206206

207-
nm.DERPMap = c.derpMap.Clone()
207+
// we don't need to set the DERPMap in the network map because we separately
208+
// send the DERPMap directly via SetDERPMap
208209
nm.Peers = c.peerConfigLocked()
209210
nm.SelfNode.Addresses = nm.Addresses
210211
nm.SelfNode.AllowedIPs = nm.Addresses

tailnet/conn.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,6 @@ func NewConn(options *Options) (conn *Conn, err error) {
116116
if len(options.Addresses) == 0 {
117117
return nil, xerrors.New("At least one IP range must be provided")
118118
}
119-
if options.DERPMap == nil {
120-
return nil, xerrors.New("DERPMap must be provided")
121-
}
122119

123120
nodePrivateKey := key.NewNode()
124121
var nodeID tailcfg.NodeID
@@ -219,7 +216,9 @@ func NewConn(options *Options) (conn *Conn, err error) {
219216
magicConn.DiscoPublicKey(),
220217
)
221218
cfgMaps.setAddresses(options.Addresses)
222-
cfgMaps.setDERPMap(options.DERPMap)
219+
if options.DERPMap != nil {
220+
cfgMaps.setDERPMap(options.DERPMap)
221+
}
223222
cfgMaps.setBlockEndpoints(options.BlockEndpoints)
224223

225224
nodeUp := newNodeUpdater(

tailnet/tailnettest/tailnettest.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,47 @@ import (
3232
//go:generate mockgen -destination ./coordinatormock.go -package tailnettest github.com/coder/coder/v2/tailnet Coordinator
3333
//go:generate mockgen -destination ./coordinateemock.go -package tailnettest github.com/coder/coder/v2/tailnet Coordinatee
3434

35+
type derpAndSTUNCfg struct {
36+
DisableSTUN bool
37+
DERPIsEmbedded bool
38+
}
39+
40+
type DERPAndStunOption func(cfg *derpAndSTUNCfg)
41+
42+
func DisableSTUN(cfg *derpAndSTUNCfg) {
43+
cfg.DisableSTUN = true
44+
}
45+
46+
func DERPIsEmbedded(cfg *derpAndSTUNCfg) {
47+
cfg.DERPIsEmbedded = true
48+
}
49+
3550
// RunDERPAndSTUN creates a DERP mapping for tests.
36-
func RunDERPAndSTUN(t *testing.T) (*tailcfg.DERPMap, *derp.Server) {
51+
func RunDERPAndSTUN(t *testing.T, opts ...DERPAndStunOption) (*tailcfg.DERPMap, *derp.Server) {
52+
cfg := new(derpAndSTUNCfg)
53+
for _, o := range opts {
54+
o(cfg)
55+
}
3756
logf := tailnet.Logger(slogtest.Make(t, nil))
3857
d := derp.NewServer(key.NewNode(), logf)
3958
server := httptest.NewUnstartedServer(derphttp.Handler(d))
4059
server.Config.ErrorLog = tslogger.StdLogger(logf)
4160
server.Config.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
4261
server.StartTLS()
43-
44-
stunAddr, stunCleanup := stuntest.ServeWithPacketListener(t, nettype.Std{})
4562
t.Cleanup(func() {
4663
server.CloseClientConnections()
4764
server.Close()
4865
d.Close()
49-
stunCleanup()
5066
})
67+
68+
stunPort := -1
69+
if !cfg.DisableSTUN {
70+
stunAddr, stunCleanup := stuntest.ServeWithPacketListener(t, nettype.Std{})
71+
t.Cleanup(func() {
72+
stunCleanup()
73+
})
74+
stunPort = stunAddr.Port
75+
}
5176
tcpAddr, ok := server.Listener.Addr().(*net.TCPAddr)
5277
if !ok {
5378
t.FailNow()
@@ -65,11 +90,12 @@ func RunDERPAndSTUN(t *testing.T) (*tailcfg.DERPMap, *derp.Server) {
6590
RegionID: 1,
6691
IPv4: "127.0.0.1",
6792
IPv6: "none",
68-
STUNPort: stunAddr.Port,
93+
STUNPort: stunPort,
6994
DERPPort: tcpAddr.Port,
7095
InsecureForTests: true,
7196
},
7297
},
98+
EmbeddedRelay: cfg.DERPIsEmbedded,
7399
},
74100
},
75101
}, d

0 commit comments

Comments
 (0)