Skip to content

Commit 5b5bc1d

Browse files
authored
feat: Add local configuration option for DERP mapping (#3996)
This allows entirely airgapped geodistributed deployments of Coder!
1 parent 6e20f9c commit 5b5bc1d

File tree

5 files changed

+81
-30
lines changed

5 files changed

+81
-30
lines changed

cli/server.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
8181
derpServerRegionName string
8282
derpServerSTUNAddrs []string
8383
derpConfigURL string
84+
derpConfigPath string
8485
promEnabled bool
8586
promAddress string
8687
pprofEnabled bool
@@ -345,7 +346,7 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
345346
if !derpServerEnabled {
346347
defaultRegion = nil
347348
}
348-
derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, derpServerSTUNAddrs, derpConfigURL)
349+
derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, derpServerSTUNAddrs, derpConfigURL, derpConfigPath)
349350
if err != nil {
350351
return xerrors.Errorf("create derp map: %w", err)
351352
}
@@ -753,6 +754,8 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
753754
cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard.")
754755
cliflag.StringVarP(root.Flags(), &derpConfigURL, "derp-config-url", "", "CODER_DERP_CONFIG_URL", "",
755756
"Specifies a URL to periodically fetch a DERP map. See: https://tailscale.com/kb/1118/custom-derp-servers/")
757+
cliflag.StringVarP(root.Flags(), &derpConfigPath, "derp-config-path", "", "CODER_DERP_CONFIG_PATH", "",
758+
"Specifies a path to read a DERP map from. See: https://tailscale.com/kb/1118/custom-derp-servers/")
756759
cliflag.BoolVarP(root.Flags(), &derpServerEnabled, "derp-server-enable", "", "CODER_DERP_SERVER_ENABLE", true, "Specifies whether to enable or disable the embedded DERP server.")
757760
cliflag.IntVarP(root.Flags(), &derpServerRegionID, "derp-server-region-id", "", "CODER_DERP_SERVER_REGION_ID", 999, "Specifies the region ID to use for the embedded DERP server.")
758761
cliflag.StringVarP(root.Flags(), &derpServerRegionCode, "derp-server-region-code", "", "CODER_DERP_SERVER_REGION_CODE", "coder", "Specifies the region code that is displayed in the Coder UI for the embedded DERP server.")
@@ -763,6 +766,7 @@ func Server(newAPI func(*coderd.Options) *coderd.API) *cobra.Command {
763766

764767
// Mark hidden while this feature is in testing!
765768
_ = root.Flags().MarkHidden("derp-config-url")
769+
_ = root.Flags().MarkHidden("derp-config-path")
766770
_ = root.Flags().MarkHidden("derp-server-enable")
767771
_ = root.Flags().MarkHidden("derp-server-region-id")
768772
_ = root.Flags().MarkHidden("derp-server-region-code")

cli/speedtest.go

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
tsspeedtest "tailscale.com/net/speedtest"
1212

1313
"cdr.dev/slog"
14+
"cdr.dev/slog/sloggers/sloghuman"
1415
"github.com/coder/coder/agent"
1516
"github.com/coder/coder/cli/cliflag"
1617
"github.com/coder/coder/cli/cliui"
@@ -51,36 +52,43 @@ func speedtest() *cobra.Command {
5152
if err != nil {
5253
return xerrors.Errorf("await agent: %w", err)
5354
}
54-
conn, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, workspaceAgent.ID)
55+
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()))
56+
if cliflag.IsSetBool(cmd, varVerbose) {
57+
logger = logger.Leveled(slog.LevelDebug)
58+
}
59+
conn, err := client.DialWorkspaceAgentTailnet(ctx, logger, workspaceAgent.ID)
5560
if err != nil {
5661
return err
5762
}
5863
defer conn.Close()
59-
if direct {
60-
ticker := time.NewTicker(time.Second)
61-
defer ticker.Stop()
62-
for {
63-
select {
64-
case <-ctx.Done():
65-
return ctx.Err()
66-
case <-ticker.C:
67-
}
68-
dur, err := conn.Ping()
69-
if err != nil {
70-
continue
71-
}
72-
tc, _ := conn.(*agent.TailnetConn)
73-
status := tc.Status()
74-
if len(status.Peers()) != 1 {
75-
continue
76-
}
77-
peer := status.Peer[status.Peers()[0]]
78-
if peer.CurAddr == "" {
79-
cmd.Printf("Waiting for a direct connection... (%dms via %s)\n", dur.Milliseconds(), peer.Relay)
80-
continue
81-
}
82-
break
64+
ticker := time.NewTicker(time.Second)
65+
defer ticker.Stop()
66+
for {
67+
select {
68+
case <-ctx.Done():
69+
return ctx.Err()
70+
case <-ticker.C:
71+
}
72+
dur, err := conn.Ping()
73+
if err != nil {
74+
continue
75+
}
76+
tc, _ := conn.(*agent.TailnetConn)
77+
status := tc.Status()
78+
if len(status.Peers()) != 1 {
79+
continue
80+
}
81+
peer := status.Peer[status.Peers()[0]]
82+
if peer.CurAddr == "" && direct {
83+
cmd.Printf("Waiting for a direct connection... (%dms via %s)\n", dur.Milliseconds(), peer.Relay)
84+
continue
85+
}
86+
via := peer.Relay
87+
if via == "" {
88+
via = "direct"
8389
}
90+
cmd.Printf("%dms via %s\n", dur.Milliseconds(), via)
91+
break
8492
}
8593
dir := tsspeedtest.Download
8694
if reverse {

tailnet/conn.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ func (c *Conn) SetNodeCallback(callback func(node *Node)) {
268268
case <-c.closed:
269269
return
270270
case node := <-queue:
271+
c.logger.Debug(context.Background(), "send node callback", slog.F("node", node))
271272
callback(node)
272273
}
273274
}
@@ -299,6 +300,8 @@ func (c *Conn) SetNodeCallback(callback func(node *Node)) {
299300
func (c *Conn) SetDERPMap(derpMap *tailcfg.DERPMap) {
300301
c.mutex.Lock()
301302
defer c.mutex.Unlock()
303+
c.netMap.DERPMap = derpMap
304+
c.logger.Debug(context.Background(), "updating derp map", slog.F("derp_map", derpMap))
302305
c.wireguardEngine.SetDERPMap(derpMap)
303306
}
304307

@@ -340,6 +343,9 @@ func (c *Conn) UpdateNodes(nodes []*Node) error {
340343
existingNode, ok := peerMap[node.ID]
341344
if ok {
342345
peerNode.Created = existingNode.Created
346+
c.logger.Debug(context.Background(), "updating peer", slog.F("peer", peerNode))
347+
} else {
348+
c.logger.Debug(context.Background(), "adding peer", slog.F("peer", peerNode))
343349
}
344350
peerMap[node.ID] = peerNode
345351
}

tailnet/derpmap.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net"
88
"net/http"
9+
"os"
910
"strconv"
1011

1112
"golang.org/x/xerrors"
@@ -14,7 +15,10 @@ import (
1415

1516
// NewDERPMap constructs a DERPMap from a set of STUN addresses and optionally a remote
1617
// URL to fetch a mapping from e.g. https://controlplane.tailscale.com/derpmap/default.
17-
func NewDERPMap(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []string, remoteURL string) (*tailcfg.DERPMap, error) {
18+
func NewDERPMap(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []string, remoteURL, localPath string) (*tailcfg.DERPMap, error) {
19+
if remoteURL != "" && localPath != "" {
20+
return nil, xerrors.New("a remote URL or local path must be specified, not both")
21+
}
1822
if region != nil {
1923
for index, stunAddr := range stunAddrs {
2024
host, rawPort, err := net.SplitHostPort(stunAddr)
@@ -53,6 +57,16 @@ func NewDERPMap(ctx context.Context, region *tailcfg.DERPRegion, stunAddrs []str
5357
return nil, xerrors.Errorf("fetch derpmap: %w", err)
5458
}
5559
}
60+
if localPath != "" {
61+
content, err := os.ReadFile(localPath)
62+
if err != nil {
63+
return nil, xerrors.Errorf("read derpmap from %q: %w", localPath, err)
64+
}
65+
err = json.Unmarshal(content, &derpMap)
66+
if err != nil {
67+
return nil, xerrors.Errorf("unmarshal derpmap: %w", err)
68+
}
69+
}
5670
if region != nil {
5771
_, conflicts := derpMap.Regions[region.RegionID]
5872
if conflicts {

tailnet/derpmap_test.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"encoding/json"
66
"net/http"
77
"net/http/httptest"
8+
"os"
9+
"path/filepath"
810
"testing"
911

1012
"github.com/stretchr/testify/require"
@@ -20,7 +22,7 @@ func TestNewDERPMap(t *testing.T) {
2022
derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
2123
RegionID: 1,
2224
Nodes: []*tailcfg.DERPNode{{}},
23-
}, []string{"stun.google.com:2345"}, "")
25+
}, []string{"stun.google.com:2345"}, "", "")
2426
require.NoError(t, err)
2527
require.Len(t, derpMap.Regions[1].Nodes, 2)
2628
})
@@ -37,7 +39,7 @@ func TestNewDERPMap(t *testing.T) {
3739
t.Cleanup(server.Close)
3840
derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
3941
RegionID: 2,
40-
}, []string{}, server.URL)
42+
}, []string{}, server.URL, "")
4143
require.NoError(t, err)
4244
require.Len(t, derpMap.Regions, 2)
4345
})
@@ -54,7 +56,24 @@ func TestNewDERPMap(t *testing.T) {
5456
t.Cleanup(server.Close)
5557
_, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
5658
RegionID: 1,
57-
}, []string{}, server.URL)
59+
}, []string{}, server.URL, "")
5860
require.Error(t, err)
5961
})
62+
t.Run("LocalPath", func(t *testing.T) {
63+
t.Parallel()
64+
localPath := filepath.Join(t.TempDir(), "derp.json")
65+
content, err := json.Marshal(&tailcfg.DERPMap{
66+
Regions: map[int]*tailcfg.DERPRegion{
67+
1: {},
68+
},
69+
})
70+
require.NoError(t, err)
71+
err = os.WriteFile(localPath, content, 0600)
72+
require.NoError(t, err)
73+
derpMap, err := tailnet.NewDERPMap(context.Background(), &tailcfg.DERPRegion{
74+
RegionID: 2,
75+
}, []string{}, "", localPath)
76+
require.NoError(t, err)
77+
require.Len(t, derpMap.Regions, 2)
78+
})
6079
}

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