WithWebsocketSupport returns an http.Handler that upgrades connections to the "derp" subprotocol to WebSockets and passes them to the DERP server. Taken from: https://github.com/tailscale/tailscale/blob/e3211ff88ba85435f70984cf67d9b353f3d650d8/cmd/derper/websocket.go#L21
(s *derp.Server, base http.Handler)
| 22 | // passes them to the DERP server. |
| 23 | // Taken from: https://github.com/tailscale/tailscale/blob/e3211ff88ba85435f70984cf67d9b353f3d650d8/cmd/derper/websocket.go#L21 |
| 24 | func WithWebsocketSupport(s *derp.Server, base http.Handler) (http.Handler, func()) { |
| 25 | var mu sync.Mutex |
| 26 | var waitGroup sync.WaitGroup |
| 27 | ctx, cancelFunc := context.WithCancel(context.Background()) |
| 28 | |
| 29 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 30 | up := strings.ToLower(r.Header.Get("Upgrade")) |
| 31 | |
| 32 | // Very early versions of Tailscale set "Upgrade: WebSocket" but didn't actually |
| 33 | // speak WebSockets (they still assumed DERP's binary framing). So to distinguish |
| 34 | // clients that actually want WebSockets, look for an explicit "derp" subprotocol. |
| 35 | if up != "websocket" || !strings.Contains(r.Header.Get("Sec-Websocket-Protocol"), "derp") { |
| 36 | base.ServeHTTP(w, r) |
| 37 | return |
| 38 | } |
| 39 | |
| 40 | mu.Lock() |
| 41 | if ctx.Err() != nil { |
| 42 | mu.Unlock() |
| 43 | return |
| 44 | } |
| 45 | waitGroup.Add(1) |
| 46 | mu.Unlock() |
| 47 | defer waitGroup.Done() |
| 48 | c, err := websocket.Accept(w, r, &websocket.AcceptOptions{ |
| 49 | Subprotocols: []string{"derp"}, |
| 50 | OriginPatterns: []string{"*"}, |
| 51 | // Disable compression because we transmit WireGuard messages that |
| 52 | // are not compressible. |
| 53 | // Additionally, Safari has a broken implementation of compression |
| 54 | // (see https://github.com/nhooyr/websocket/issues/218) that makes |
| 55 | // enabling it actively harmful. |
| 56 | CompressionMode: websocket.CompressionDisabled, |
| 57 | }) |
| 58 | if err != nil { |
| 59 | log.Printf("websocket.Accept: %v", err) |
| 60 | return |
| 61 | } |
| 62 | defer c.Close(websocket.StatusInternalError, "closing") |
| 63 | if c.Subprotocol() != "derp" { |
| 64 | c.Close(websocket.StatusPolicyViolation, "client must speak the derp subprotocol") |
| 65 | return |
| 66 | } |
| 67 | wc := websocket.NetConn(ctx, c, websocket.MessageBinary) |
| 68 | brw := bufio.NewReadWriter(bufio.NewReader(wc), bufio.NewWriter(wc)) |
| 69 | s.Accept(ctx, wc, brw, r.RemoteAddr) |
| 70 | }), func() { |
| 71 | cancelFunc() |
| 72 | mu.Lock() |
| 73 | waitGroup.Wait() |
| 74 | mu.Unlock() |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | type DERPMapRewriter interface { |
| 79 | RewriteDERPMap(derpMap *tailcfg.DERPMap) |