mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
feat: add server flag to force DERP to use always websockets (#9238)
This commit is contained in:
12
coderd/apidoc/docs.go
generated
12
coderd/apidoc/docs.go
generated
@ -6416,6 +6416,9 @@ const docTemplate = `{
|
||||
"$ref": "#/definitions/codersdk.WorkspaceApp"
|
||||
}
|
||||
},
|
||||
"derp_force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derpmap": {
|
||||
"$ref": "#/definitions/tailcfg.DERPMap"
|
||||
},
|
||||
@ -7781,6 +7784,9 @@ const docTemplate = `{
|
||||
"block_direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -10710,6 +10716,9 @@ const docTemplate = `{
|
||||
"codersdk.WorkspaceAgentConnectionInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"derp_force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_map": {
|
||||
"$ref": "#/definitions/tailcfg.DERPMap"
|
||||
},
|
||||
@ -11973,6 +11982,9 @@ const docTemplate = `{
|
||||
"app_security_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"derp_force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_map": {
|
||||
"$ref": "#/definitions/tailcfg.DERPMap"
|
||||
},
|
||||
|
12
coderd/apidoc/swagger.json
generated
12
coderd/apidoc/swagger.json
generated
@ -5656,6 +5656,9 @@
|
||||
"$ref": "#/definitions/codersdk.WorkspaceApp"
|
||||
}
|
||||
},
|
||||
"derp_force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derpmap": {
|
||||
"$ref": "#/definitions/tailcfg.DERPMap"
|
||||
},
|
||||
@ -6936,6 +6939,9 @@
|
||||
"block_direct": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -9712,6 +9718,9 @@
|
||||
"codersdk.WorkspaceAgentConnectionInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"derp_force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_map": {
|
||||
"$ref": "#/definitions/tailcfg.DERPMap"
|
||||
},
|
||||
@ -10934,6 +10943,9 @@
|
||||
"app_security_key": {
|
||||
"type": "string"
|
||||
},
|
||||
"derp_force_websockets": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"derp_map": {
|
||||
"$ref": "#/definitions/tailcfg.DERPMap"
|
||||
},
|
||||
|
@ -405,6 +405,7 @@ func New(options *Options) *API {
|
||||
options.Logger,
|
||||
options.DERPServer,
|
||||
api.DERPMap,
|
||||
options.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
||||
func(context.Context) (tailnet.MultiAgentConn, error) {
|
||||
return (*api.TailnetCoordinator.Load()).ServeMultiAgent(uuid.New()), nil
|
||||
},
|
||||
|
@ -7,9 +7,13 @@ import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
@ -18,8 +22,13 @@ import (
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
|
||||
"github.com/coder/coder/v2/agent"
|
||||
"github.com/coder/coder/v2/buildinfo"
|
||||
"github.com/coder/coder/v2/coderd"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
"github.com/coder/coder/v2/provisioner/echo"
|
||||
"github.com/coder/coder/v2/tailnet"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
@ -119,6 +128,91 @@ func TestDERP(t *testing.T) {
|
||||
w2.Close()
|
||||
}
|
||||
|
||||
func TestDERPForceWebSockets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dv := coderdtest.DeploymentValues(t)
|
||||
dv.DERP.Config.ForceWebSockets = true
|
||||
dv.DERP.Config.BlockDirect = true // to ensure the test always uses DERP
|
||||
|
||||
// Manually create a server so we can influence the HTTP handler.
|
||||
options := &coderdtest.Options{
|
||||
DeploymentValues: dv,
|
||||
}
|
||||
setHandler, cancelFunc, serverURL, newOptions := coderdtest.NewOptions(t, options)
|
||||
coderAPI := coderd.New(newOptions)
|
||||
t.Cleanup(func() {
|
||||
cancelFunc()
|
||||
_ = coderAPI.Close()
|
||||
})
|
||||
|
||||
// Set the HTTP handler to a custom one that ensures all /derp calls are
|
||||
// WebSockets and not `Upgrade: derp`.
|
||||
var upgradeCount int64
|
||||
setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasPrefix(r.URL.Path, "/derp") {
|
||||
up := r.Header.Get("Upgrade")
|
||||
if up != "" && up != "websocket" {
|
||||
t.Errorf("expected Upgrade: websocket, got %q", up)
|
||||
} else {
|
||||
atomic.AddInt64(&upgradeCount, 1)
|
||||
}
|
||||
}
|
||||
|
||||
coderAPI.RootHandler.ServeHTTP(rw, r)
|
||||
}))
|
||||
|
||||
// Start a provisioner daemon.
|
||||
provisionerCloser := coderdtest.NewProvisionerDaemon(t, coderAPI)
|
||||
t.Cleanup(func() {
|
||||
_ = provisionerCloser.Close()
|
||||
})
|
||||
|
||||
client := codersdk.New(serverURL)
|
||||
t.Cleanup(func() {
|
||||
client.HTTPClient.CloseIdleConnections()
|
||||
})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
gen, err := client.WorkspaceAgentConnectionInfoGeneric(context.Background())
|
||||
require.NoError(t, err)
|
||||
t.Log(spew.Sdump(gen))
|
||||
|
||||
authToken := uuid.NewString()
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: echo.ProvisionComplete,
|
||||
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
|
||||
})
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
|
||||
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
|
||||
|
||||
agentClient := agentsdk.New(client.URL)
|
||||
agentClient.SetSessionToken(authToken)
|
||||
agentCloser := agent.New(agent.Options{
|
||||
Client: agentClient,
|
||||
Logger: slogtest.Make(t, nil).Named("agent").Leveled(slog.LevelDebug),
|
||||
})
|
||||
defer func() {
|
||||
_ = agentCloser.Close()
|
||||
}()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
|
||||
conn, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = conn.Close()
|
||||
}()
|
||||
conn.AwaitReachable(ctx)
|
||||
|
||||
require.GreaterOrEqual(t, atomic.LoadInt64(&upgradeCount), int64(1), "expected at least one /derp call")
|
||||
}
|
||||
|
||||
func TestDERPLatencyCheck(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, nil)
|
||||
|
@ -326,7 +326,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
|
||||
stunAddresses []string
|
||||
dvStunAddresses = options.DeploymentValues.DERP.Server.STUNAddresses.Value()
|
||||
)
|
||||
if len(dvStunAddresses) == 0 || (len(dvStunAddresses) == 1 && dvStunAddresses[0] == "stun.l.google.com:19302") {
|
||||
if len(dvStunAddresses) == 0 || dvStunAddresses[0] == "stun.l.google.com:19302" {
|
||||
stunAddr, stunCleanup := stuntest.ServeWithPacketListener(t, nettype.Std{})
|
||||
stunAddr.IP = net.ParseIP("127.0.0.1")
|
||||
t.Cleanup(stunCleanup)
|
||||
|
@ -45,6 +45,7 @@ func NewServerTailnet(
|
||||
logger slog.Logger,
|
||||
derpServer *derp.Server,
|
||||
derpMapFn func() *tailcfg.DERPMap,
|
||||
derpForceWebSockets bool,
|
||||
getMultiAgent func(context.Context) (tailnet.MultiAgentConn, error),
|
||||
cache *wsconncache.Cache,
|
||||
traceProvider trace.TracerProvider,
|
||||
@ -52,9 +53,10 @@ func NewServerTailnet(
|
||||
logger = logger.Named("servertailnet")
|
||||
originalDerpMap := derpMapFn()
|
||||
conn, err := tailnet.NewConn(&tailnet.Options{
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
||||
DERPMap: originalDerpMap,
|
||||
Logger: logger,
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
||||
DERPMap: originalDerpMap,
|
||||
DERPForceWebSockets: derpForceWebSockets,
|
||||
Logger: logger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("create tailnet conn: %w", err)
|
||||
|
@ -232,6 +232,7 @@ func setupAgent(t *testing.T, agentAddresses []netip.Prefix) (uuid.UUID, agent.A
|
||||
logger,
|
||||
derpServer,
|
||||
func() *tailcfg.DERPMap { return manifest.DERPMap },
|
||||
false,
|
||||
func(context.Context) (tailnet.MultiAgentConn, error) { return coord.ServeMultiAgent(uuid.New()), nil },
|
||||
cache,
|
||||
trace.NewNoopTracerProvider(),
|
||||
|
@ -167,6 +167,7 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request)
|
||||
AgentID: apiAgent.ID,
|
||||
Apps: convertApps(dbApps),
|
||||
DERPMap: api.DERPMap(),
|
||||
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
||||
GitAuthConfigs: len(api.GitAuthConfigs),
|
||||
EnvironmentVariables: apiAgent.EnvironmentVariables,
|
||||
StartupScript: apiAgent.StartupScript,
|
||||
@ -733,10 +734,11 @@ func (api *API) _dialWorkspaceAgentTailnet(agentID uuid.UUID) (*codersdk.Workspa
|
||||
|
||||
derpMap := api.DERPMap()
|
||||
conn, err := tailnet.NewConn(&tailnet.Options{
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
||||
DERPMap: api.DERPMap(),
|
||||
Logger: api.Logger.Named("net.tailnet"),
|
||||
BlockEndpoints: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
||||
DERPMap: api.DERPMap(),
|
||||
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
||||
Logger: api.Logger.Named("net.tailnet"),
|
||||
BlockEndpoints: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
||||
})
|
||||
if err != nil {
|
||||
_ = clientConn.Close()
|
||||
@ -831,6 +833,7 @@ func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{
|
||||
DERPMap: api.DERPMap(),
|
||||
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
||||
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
||||
})
|
||||
}
|
||||
@ -851,6 +854,7 @@ func (api *API) workspaceAgentConnectionGeneric(rw http.ResponseWriter, r *http.
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{
|
||||
DERPMap: api.DERPMap(),
|
||||
DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(),
|
||||
DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(),
|
||||
})
|
||||
}
|
||||
|
@ -179,9 +179,10 @@ func setupAgent(t *testing.T, manifest agentsdk.Manifest, ptyTimeout time.Durati
|
||||
_ = closer.Close()
|
||||
})
|
||||
conn, err := tailnet.NewConn(&tailnet.Options{
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
||||
DERPMap: manifest.DERPMap,
|
||||
Logger: slogtest.Make(t, nil).Named("tailnet").Leveled(slog.LevelDebug),
|
||||
Addresses: []netip.Prefix{netip.PrefixFrom(tailnet.IP(), 128)},
|
||||
DERPMap: manifest.DERPMap,
|
||||
DERPForceWebSockets: manifest.DERPForceWebSockets,
|
||||
Logger: slogtest.Make(t, nil).Named("tailnet").Leveled(slog.LevelDebug),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
clientConn, serverConn := net.Pipe()
|
||||
|
Reference in New Issue
Block a user