Skip to content

Commit c2cd7d4

Browse files
committed
feat: swap over to websockets if initial derp exchange fails
1 parent 446fc10 commit c2cd7d4

File tree

5 files changed

+37
-7
lines changed

5 files changed

+37
-7
lines changed

derp/derphttp/derphttp_client.go

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"runtime"
2626
"strings"
2727
"sync"
28+
"sync/atomic"
2829
"time"
2930

3031
"go4.org/mem"
@@ -47,10 +48,11 @@ import (
4748
// Send/Recv will completely re-establish the connection (unless Close
4849
// has been called).
4950
type Client struct {
50-
TLSConfig *tls.Config // optional; nil means default
51-
DNSCache *dnscache.Resolver // optional; nil means no caching
52-
MeshKey string // optional; for trusted clients
53-
IsProber bool // optional; for probers to optional declare themselves as such
51+
TLSConfig *tls.Config // optional; nil means default
52+
DNSCache *dnscache.Resolver // optional; nil means no caching
53+
MeshKey string // optional; for trusted clients
54+
IsProber bool // optional; for probers to optional declare themselves as such
55+
ForceWebsocket atomic.Pointer[string] // optional; set if the server has failed to upgrade the connection on the DERP server
5456

5557
privateKey key.NodePrivate
5658
logf logger.Logf
@@ -230,14 +232,14 @@ func (c *Client) preferIPv6() bool {
230232
// dialWebsocketFunc is non-nil (set by websocket.go's init) when compiled in.
231233
var dialWebsocketFunc func(ctx context.Context, urlStr string) (net.Conn, error)
232234

233-
func useWebsockets() bool {
235+
func (c *Client) useWebsockets() bool {
234236
if runtime.GOOS == "js" {
235237
return true
236238
}
237239
if dialWebsocketFunc != nil {
238240
return envknob.Bool("TS_DEBUG_DERP_WS_CLIENT")
239241
}
240-
return false
242+
return c.ForceWebsocket.Load() != nil
241243
}
242244

243245
func (c *Client) connect(ctx context.Context, caller string) (client *derp.Client, connGen int, err error) {
@@ -293,7 +295,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
293295

294296
var node *tailcfg.DERPNode // nil when using c.url to dial
295297
switch {
296-
case useWebsockets():
298+
case c.useWebsockets():
297299
var urlStr string
298300
if c.url != nil {
299301
urlStr = c.url.String()
@@ -435,6 +437,15 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
435437
if resp.StatusCode != http.StatusSwitchingProtocols {
436438
b, _ := io.ReadAll(resp.Body)
437439
resp.Body.Close()
440+
441+
// Only on StatusCode < 500 in case a gateway timeout
442+
// or network intermittency occurs!
443+
if resp.StatusCode < 500 {
444+
reason := fmt.Sprintf("GET failed with status code %d: %s", resp.StatusCode, b)
445+
c.logf("We'll use WebSockets on the next connection attempt. A proxy could be disallowing the use of 'Upgrade: derp': %s", reason)
446+
c.ForceWebsocket.Store(&reason)
447+
}
448+
438449
return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
439450
}
440451
}

tailcfg/tailcfg.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,13 @@ type NetInfo struct {
628628
// the control plane.
629629
DERPLatency map[string]float64 `json:",omitempty"`
630630

631+
// DERPForcedWebsocket contains regions when the client is
632+
// forced to use a WebSocket because of a failed connection
633+
// upgrade.
634+
//
635+
// The string value is the failure reason.
636+
DERPForcedWebsocket map[int]string `json:",omitempty"`
637+
631638
// Update BasicallyEqual when adding fields.
632639
}
633640

tailcfg/tailcfg_clone.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tailcfg/tailcfg_view.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wgengine/magicsock/magicsock.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,7 @@ func (c *Conn) updateNetInfo(ctx context.Context) (*netcheck.Report, error) {
829829

830830
ni := &tailcfg.NetInfo{
831831
DERPLatency: map[string]float64{},
832+
DERPForcedWebsocket: map[int]string{},
832833
MappingVariesByDestIP: report.MappingVariesByDestIP,
833834
HairPinning: report.HairPinning,
834835
UPnP: report.UPnP,
@@ -842,6 +843,15 @@ func (c *Conn) updateNetInfo(ctx context.Context) (*netcheck.Report, error) {
842843
for rid, d := range report.RegionV6Latency {
843844
ni.DERPLatency[fmt.Sprintf("%d-v6", rid)] = d.Seconds()
844845
}
846+
c.mu.Lock()
847+
for rid, derp := range c.activeDerp {
848+
usingWebsockets := derp.c.ForceWebsocket.Load()
849+
if usingWebsockets != nil {
850+
ni.DERPForcedWebsocket[rid] = *usingWebsockets
851+
}
852+
}
853+
c.mu.Unlock()
854+
845855
ni.WorkingIPv6.Set(report.IPv6)
846856
ni.OSHasIPv6.Set(report.OSHasIPv6)
847857
ni.WorkingUDP.Set(report.UDP)

0 commit comments

Comments
 (0)