Skip to content

Commit 0e3e496

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

File tree

5 files changed

+39
-6
lines changed

5 files changed

+39
-6
lines changed

derp/derphttp/derphttp_client.go

Lines changed: 20 additions & 6 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,10 +232,13 @@ 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
}
239+
if c.ForceWebsocket.Load() != nil {
240+
return true
241+
}
237242
if dialWebsocketFunc != nil {
238243
return envknob.Bool("TS_DEBUG_DERP_WS_CLIENT")
239244
}
@@ -293,7 +298,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
293298

294299
var node *tailcfg.DERPNode // nil when using c.url to dial
295300
switch {
296-
case useWebsockets():
301+
case c.useWebsockets():
297302
var urlStr string
298303
if c.url != nil {
299304
urlStr = c.url.String()
@@ -435,6 +440,15 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
435440
if resp.StatusCode != http.StatusSwitchingProtocols {
436441
b, _ := io.ReadAll(resp.Body)
437442
resp.Body.Close()
443+
444+
// Only on StatusCode < 500 in case a gateway timeout
445+
// or network intermittency occurs!
446+
if resp.StatusCode < 500 {
447+
reason := fmt.Sprintf("GET failed with status code %d: %s", resp.StatusCode, b)
448+
c.logf("We'll use WebSockets on the next connection attempt. A proxy could be disallowing the use of 'Upgrade: derp': %s", reason)
449+
c.ForceWebsocket.Store(&reason)
450+
}
451+
438452
return nil, 0, fmt.Errorf("GET failed: %v: %s", err, b)
439453
}
440454
}

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)