Skip to content

Commit a1b87a6

Browse files
authored
fix: use client preferred URL for the default DERP (#18911)
The agentsdk currently does a remap of the DERP map to change the EmbeddedRelay node's URL to match the agent's access URL. This PR makes changes to the `workspacesdk` (used by clients like the CLI) and `vpn` (used by Coder Desktop) to match this behavior. This enables us the ability to try Coder clients in dogfood over a VPN without changing the global access URL.
1 parent fb00cd2 commit a1b87a6

File tree

11 files changed

+248
-56
lines changed

11 files changed

+248
-56
lines changed

agent/agent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ type Client interface {
9898
ConnectRPC26(ctx context.Context) (
9999
proto.DRPCAgentClient26, tailnetproto.DRPCTailnetClient26, error,
100100
)
101-
RewriteDERPMap(derpMap *tailcfg.DERPMap)
101+
tailnet.DERPMapRewriter
102102
}
103103

104104
type Agent interface {

coderd/tailnet.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func NewServerTailnet(
9898
controller := tailnet.NewController(logger, dialer)
9999
// it's important to set the DERPRegionDialer above _before_ we set the DERP map so that if
100100
// there is an embedded relay, we use the local in-memory dialer.
101-
controller.DERPCtrl = tailnet.NewBasicDERPController(logger, conn)
101+
controller.DERPCtrl = tailnet.NewBasicDERPController(logger, nil, conn)
102102
coordCtrl := NewMultiAgentController(serverCtx, logger, tracer, conn)
103103
controller.CoordCtrl = coordCtrl
104104
// TODO: support controller.TelemetryCtrl

codersdk/agentsdk/agentsdk.go

Lines changed: 6 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"net/http"
99
"net/http/cookiejar"
1010
"net/url"
11-
"strconv"
1211
"time"
1312

1413
"cloud.google.com/go/compute/metadata"
@@ -27,6 +26,7 @@ import (
2726
"github.com/coder/coder/v2/coderd/httpapi"
2827
"github.com/coder/coder/v2/codersdk"
2928
"github.com/coder/coder/v2/codersdk/drpcsdk"
29+
"github.com/coder/coder/v2/tailnet"
3030
tailnetproto "github.com/coder/coder/v2/tailnet/proto"
3131
)
3232

@@ -126,40 +126,13 @@ type Script struct {
126126
Script string `json:"script"`
127127
}
128128

129-
// RewriteDERPMap rewrites the DERP map to use the access URL of the SDK as the
130-
// "embedded relay" access URL. The passed derp map is modified in place.
129+
// RewriteDERPMap rewrites the DERP map to use the configured access URL of the
130+
// agent as the "embedded relay" access URL.
131131
//
132-
// Agents can provide an arbitrary access URL that may be different that the
133-
// globally configured one. This breaks the built-in DERP, which would continue
134-
// to reference the global access URL.
132+
// See tailnet.RewriteDERPMapDefaultRelay for more details on why this is
133+
// necessary.
135134
func (c *Client) RewriteDERPMap(derpMap *tailcfg.DERPMap) {
136-
accessingPort := c.SDK.URL.Port()
137-
if accessingPort == "" {
138-
accessingPort = "80"
139-
if c.SDK.URL.Scheme == "https" {
140-
accessingPort = "443"
141-
}
142-
}
143-
accessPort, err := strconv.Atoi(accessingPort)
144-
if err != nil {
145-
// this should never happen because URL.Port() returns the empty string if the port is not
146-
// valid.
147-
c.SDK.Logger().Critical(context.Background(), "failed to parse URL port", slog.F("port", accessingPort))
148-
}
149-
for _, region := range derpMap.Regions {
150-
if !region.EmbeddedRelay {
151-
continue
152-
}
153-
154-
for _, node := range region.Nodes {
155-
if node.STUNOnly {
156-
continue
157-
}
158-
node.HostName = c.SDK.URL.Hostname()
159-
node.DERPPort = accessPort
160-
node.ForceHTTP = c.SDK.URL.Scheme == "http"
161-
}
162-
}
135+
tailnet.RewriteDERPMapDefaultRelay(context.Background(), c.SDK.Logger(), derpMap, c.SDK.URL)
163136
}
164137

165138
// ConnectRPC20 returns a dRPC client to the Agent API v2.0. Notably, it is missing

codersdk/agentsdk/agentsdk_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"io"
66
"net/http"
77
"net/http/httptest"
8+
"net/url"
89
"testing"
910

1011
"github.com/google/uuid"
1112
"github.com/stretchr/testify/require"
13+
"tailscale.com/tailcfg"
1214

1315
"cdr.dev/slog/sloggers/slogtest"
1416
"github.com/coder/coder/v2/codersdk/agentsdk"
@@ -120,3 +122,31 @@ func TestStreamAgentReinitEvents(t *testing.T) {
120122
require.ErrorIs(t, receiveErr, context.Canceled)
121123
})
122124
}
125+
126+
func TestRewriteDERPMap(t *testing.T) {
127+
t.Parallel()
128+
// This test ensures that RewriteDERPMap mutates built-in DERPs with the
129+
// client access URL.
130+
dm := &tailcfg.DERPMap{
131+
Regions: map[int]*tailcfg.DERPRegion{
132+
1: {
133+
EmbeddedRelay: true,
134+
RegionID: 1,
135+
Nodes: []*tailcfg.DERPNode{{
136+
HostName: "bananas.org",
137+
DERPPort: 1,
138+
}},
139+
},
140+
},
141+
}
142+
parsed, err := url.Parse("https://coconuts.org:44558")
143+
require.NoError(t, err)
144+
client := agentsdk.New(parsed)
145+
client.RewriteDERPMap(dm)
146+
region := dm.Regions[1]
147+
require.True(t, region.EmbeddedRelay)
148+
require.Len(t, region.Nodes, 1)
149+
node := region.Nodes[0]
150+
require.Equal(t, "coconuts.org", node.HostName)
151+
require.Equal(t, 44558, node.DERPPort)
152+
}

codersdk/workspacesdk/workspacesdk.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,15 @@ type DialAgentOptions struct {
193193
EnableTelemetry bool
194194
}
195195

196+
// RewriteDERPMap rewrites the DERP map to use the configured access URL of the
197+
// client as the "embedded relay" access URL.
198+
//
199+
// See tailnet.RewriteDERPMapDefaultRelay for more details on why this is
200+
// necessary.
201+
func (c *Client) RewriteDERPMap(derpMap *tailcfg.DERPMap) {
202+
tailnet.RewriteDERPMapDefaultRelay(context.Background(), c.client.Logger(), derpMap, c.client.URL)
203+
}
204+
196205
func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *DialAgentOptions) (agentConn *AgentConn, err error) {
197206
if options == nil {
198207
options = &DialAgentOptions{}
@@ -248,6 +257,8 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *
248257
telemetrySink = basicTel
249258
controller.TelemetryCtrl = basicTel
250259
}
260+
261+
c.RewriteDERPMap(connInfo.DERPMap)
251262
conn, err := tailnet.NewConn(&tailnet.Options{
252263
Addresses: []netip.Prefix{netip.PrefixFrom(ip, 128)},
253264
DERPMap: connInfo.DERPMap,
@@ -270,7 +281,7 @@ func (c *Client) DialAgent(dialCtx context.Context, agentID uuid.UUID, options *
270281
coordCtrl := tailnet.NewTunnelSrcCoordController(options.Logger, conn)
271282
coordCtrl.AddDestination(agentID)
272283
controller.CoordCtrl = coordCtrl
273-
controller.DERPCtrl = tailnet.NewBasicDERPController(options.Logger, conn)
284+
controller.DERPCtrl = tailnet.NewBasicDERPController(options.Logger, c, conn)
274285
controller.Run(ctx)
275286

276287
options.Logger.Debug(ctx, "running tailnet API v2+ connector")

codersdk/workspacesdk/workspacesdk_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919

2020
"github.com/coder/coder/v2/coderd/httpapi"
2121
"github.com/coder/coder/v2/codersdk"
22-
"github.com/coder/coder/v2/codersdk/agentsdk"
2322
"github.com/coder/coder/v2/codersdk/workspacesdk"
2423
"github.com/coder/coder/v2/tailnet"
2524
"github.com/coder/coder/v2/testutil"
@@ -43,7 +42,7 @@ func TestWorkspaceRewriteDERPMap(t *testing.T) {
4342
}
4443
parsed, err := url.Parse("https://coconuts.org:44558")
4544
require.NoError(t, err)
46-
client := agentsdk.New(parsed)
45+
client := workspacesdk.New(codersdk.New(parsed))
4746
client.RewriteDERPMap(dm)
4847
region := dm.Regions[1]
4948
require.True(t, region.EmbeddedRelay)

tailnet/controllers.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -568,13 +568,15 @@ type DERPMapSetter interface {
568568
}
569569

570570
type basicDERPController struct {
571-
logger slog.Logger
572-
setter DERPMapSetter
571+
logger slog.Logger
572+
rewriter DERPMapRewriter // optional
573+
setter DERPMapSetter
573574
}
574575

575576
func (b *basicDERPController) New(client DERPClient) CloserWaiter {
576577
l := &derpSetLoop{
577578
logger: b.logger,
579+
rewriter: b.rewriter,
578580
setter: b.setter,
579581
client: client,
580582
errChan: make(chan error, 1),
@@ -584,17 +586,23 @@ func (b *basicDERPController) New(client DERPClient) CloserWaiter {
584586
return l
585587
}
586588

587-
func NewBasicDERPController(logger slog.Logger, setter DERPMapSetter) DERPController {
589+
// NewBasicDERPController creates a DERP controller that rewrites the DERP map
590+
// with the provided rewriter before setting it on the provided setter.
591+
//
592+
// The rewriter is optional and can be nil.
593+
func NewBasicDERPController(logger slog.Logger, rewriter DERPMapRewriter, setter DERPMapSetter) DERPController {
588594
return &basicDERPController{
589-
logger: logger,
590-
setter: setter,
595+
logger: logger,
596+
rewriter: rewriter,
597+
setter: setter,
591598
}
592599
}
593600

594601
type derpSetLoop struct {
595-
logger slog.Logger
596-
setter DERPMapSetter
597-
client DERPClient
602+
logger slog.Logger
603+
rewriter DERPMapRewriter // optional
604+
setter DERPMapSetter
605+
client DERPClient
598606

599607
sync.Mutex
600608
closed bool
@@ -640,6 +648,9 @@ func (l *derpSetLoop) recvLoop() {
640648
return
641649
}
642650
l.logger.Debug(context.Background(), "got new DERP Map", slog.F("derp_map", dm))
651+
if l.rewriter != nil {
652+
l.rewriter.RewriteDERPMap(dm)
653+
}
643654
l.setter.SetDERPMap(dm)
644655
}
645656
}

tailnet/controllers_test.go

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ func TestNewBasicDERPController_Mainline(t *testing.T) {
586586
t.Parallel()
587587
fs := make(chan *tailcfg.DERPMap)
588588
logger := testutil.Logger(t)
589-
uut := tailnet.NewBasicDERPController(logger, fakeSetter(fs))
589+
uut := tailnet.NewBasicDERPController(logger, nil, fakeSetter(fs))
590590
fc := fakeDERPClient{
591591
ch: make(chan *tailcfg.DERPMap),
592592
}
@@ -609,7 +609,7 @@ func TestNewBasicDERPController_RecvErr(t *testing.T) {
609609
t.Parallel()
610610
fs := make(chan *tailcfg.DERPMap)
611611
logger := testutil.Logger(t)
612-
uut := tailnet.NewBasicDERPController(logger, fakeSetter(fs))
612+
uut := tailnet.NewBasicDERPController(logger, nil, fakeSetter(fs))
613613
expectedErr := xerrors.New("a bad thing happened")
614614
fc := fakeDERPClient{
615615
ch: make(chan *tailcfg.DERPMap),
@@ -1041,7 +1041,7 @@ func TestController_Disconnects(t *testing.T) {
10411041
// darwin can be slow sometimes.
10421042
tailnet.WithGracefulTimeout(5*time.Second))
10431043
uut.CoordCtrl = tailnet.NewAgentCoordinationController(logger.Named("coord_ctrl"), fConn)
1044-
uut.DERPCtrl = tailnet.NewBasicDERPController(logger.Named("derp_ctrl"), fConn)
1044+
uut.DERPCtrl = tailnet.NewBasicDERPController(logger.Named("derp_ctrl"), nil, fConn)
10451045
uut.Run(ctx)
10461046

10471047
call := testutil.TryReceive(testCtx, t, fCoord.CoordinateCalls)
@@ -1945,6 +1945,52 @@ func TestTunnelAllWorkspaceUpdatesController_HandleErrors(t *testing.T) {
19451945
}
19461946
}
19471947

1948+
func TestBasicDERPController_RewriteDERPMap(t *testing.T) {
1949+
t.Parallel()
1950+
ctx := testutil.Context(t, testutil.WaitShort)
1951+
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
1952+
1953+
testDERPMap := &tailcfg.DERPMap{
1954+
Regions: map[int]*tailcfg.DERPRegion{
1955+
1: {
1956+
RegionID: 1,
1957+
},
1958+
},
1959+
}
1960+
1961+
// Ensure the fake rewriter works as expected.
1962+
rewriter := &fakeDERPMapRewriter{
1963+
ctx: ctx,
1964+
calls: make(chan rewriteDERPMapCall, 16),
1965+
}
1966+
rewriter.RewriteDERPMap(testDERPMap)
1967+
rewriteCall := testutil.TryReceive(ctx, t, rewriter.calls)
1968+
require.Same(t, testDERPMap, rewriteCall.derpMap)
1969+
1970+
derpClient := &fakeDERPClient{
1971+
ch: make(chan *tailcfg.DERPMap),
1972+
err: nil,
1973+
}
1974+
defer derpClient.Close()
1975+
1976+
derpSetter := &fakeDERPMapSetter{
1977+
ctx: ctx,
1978+
calls: make(chan *setDERPMapCall, 16),
1979+
}
1980+
1981+
derpCtrl := tailnet.NewBasicDERPController(logger, rewriter, derpSetter)
1982+
derpCtrl.New(derpClient)
1983+
1984+
// Simulate receiving a new DERP map from the server, which should be passed
1985+
// to the rewriter and setter.
1986+
testDERPMap = testDERPMap.Clone() // make a new pointer
1987+
derpClient.ch <- testDERPMap
1988+
rewriteCall = testutil.TryReceive(ctx, t, rewriter.calls)
1989+
require.Same(t, testDERPMap, rewriteCall.derpMap)
1990+
setterCall := testutil.TryReceive(ctx, t, derpSetter.calls)
1991+
require.Same(t, testDERPMap, setterCall.derpMap)
1992+
}
1993+
19481994
type fakeWorkspaceUpdatesController struct {
19491995
ctx context.Context
19501996
t testing.TB
@@ -2040,3 +2086,45 @@ type fakeCloser struct{}
20402086
func (fakeCloser) Close() error {
20412087
return nil
20422088
}
2089+
2090+
type fakeDERPMapRewriter struct {
2091+
ctx context.Context
2092+
calls chan rewriteDERPMapCall
2093+
}
2094+
2095+
var _ tailnet.DERPMapRewriter = &fakeDERPMapRewriter{}
2096+
2097+
type rewriteDERPMapCall struct {
2098+
derpMap *tailcfg.DERPMap
2099+
}
2100+
2101+
func (f *fakeDERPMapRewriter) RewriteDERPMap(derpMap *tailcfg.DERPMap) {
2102+
call := rewriteDERPMapCall{
2103+
derpMap: derpMap,
2104+
}
2105+
select {
2106+
case f.calls <- call:
2107+
case <-f.ctx.Done():
2108+
}
2109+
}
2110+
2111+
type fakeDERPMapSetter struct {
2112+
ctx context.Context
2113+
calls chan *setDERPMapCall
2114+
}
2115+
2116+
var _ tailnet.DERPMapSetter = &fakeDERPMapSetter{}
2117+
2118+
type setDERPMapCall struct {
2119+
derpMap *tailcfg.DERPMap
2120+
}
2121+
2122+
func (f *fakeDERPMapSetter) SetDERPMap(derpMap *tailcfg.DERPMap) {
2123+
call := &setDERPMapCall{
2124+
derpMap: derpMap,
2125+
}
2126+
select {
2127+
case <-f.ctx.Done():
2128+
case f.calls <- call:
2129+
}
2130+
}

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