Updates #3157 Change-Id: I337a919a3b350bc7bd9af567b49c4d5d6616abdd Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>main
parent
0b62f26349
commit
505f844a43
@ -0,0 +1,52 @@ |
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"expvar" |
||||
"log" |
||||
"net/http" |
||||
"strings" |
||||
|
||||
"nhooyr.io/websocket" |
||||
"tailscale.com/derp" |
||||
"tailscale.com/derp/wsconn" |
||||
) |
||||
|
||||
var counterWebSocketAccepts = expvar.NewInt("derp_websocket_accepts") |
||||
|
||||
// addWebSocketSupport returns a Handle wrapping base that adds WebSocket server support.
|
||||
func addWebSocketSupport(s *derp.Server, base http.Handler) http.Handler { |
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
up := strings.ToLower(r.Header.Get("Upgrade")) |
||||
|
||||
// Very early versions of Tailscale set "Upgrade: WebSocket" but didn't actually
|
||||
// speak WebSockets (they still assumed DERP's binary framining). So to distinguish
|
||||
// clients that actually want WebSockets, look for an explicit "derp" subprotocol.
|
||||
if up != "websocket" || !strings.Contains(r.Header.Get("Sec-Websocket-Protocol"), "derp") { |
||||
base.ServeHTTP(w, r) |
||||
return |
||||
} |
||||
|
||||
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{ |
||||
Subprotocols: []string{"derp"}, |
||||
OriginPatterns: []string{"*"}, |
||||
}) |
||||
if err != nil { |
||||
log.Printf("websocket.Accept: %v", err) |
||||
return |
||||
} |
||||
defer c.Close(websocket.StatusInternalError, "closing") |
||||
if c.Subprotocol() != "derp" { |
||||
c.Close(websocket.StatusPolicyViolation, "client must speak the derp subprotocol") |
||||
return |
||||
} |
||||
counterWebSocketAccepts.Add(1) |
||||
wc := wsconn.New(c) |
||||
brw := bufio.NewReadWriter(bufio.NewReader(wc), bufio.NewWriter(wc)) |
||||
s.Accept(wc, brw, r.RemoteAddr) |
||||
}) |
||||
} |
||||
@ -0,0 +1,33 @@ |
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || js
|
||||
// +build linux js
|
||||
|
||||
package derphttp |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
"net" |
||||
|
||||
"nhooyr.io/websocket" |
||||
"tailscale.com/derp/wsconn" |
||||
) |
||||
|
||||
func init() { |
||||
dialWebsocketFunc = dialWebsocket |
||||
} |
||||
|
||||
func dialWebsocket(ctx context.Context, urlStr string) (net.Conn, error) { |
||||
c, res, err := websocket.Dial(ctx, urlStr, &websocket.DialOptions{ |
||||
Subprotocols: []string{"derp"}, |
||||
}) |
||||
if err != nil { |
||||
log.Printf("websocket Dial: %v, %+v", err, res) |
||||
return nil, err |
||||
} |
||||
log.Printf("websocket: connected to %v", urlStr) |
||||
return wsconn.New(c), nil |
||||
} |
||||
@ -0,0 +1,104 @@ |
||||
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package wsconn contains an adapter type that turns
|
||||
// a websocket connection into a net.Conn.
|
||||
package wsconn |
||||
|
||||
import ( |
||||
"context" |
||||
"net" |
||||
"sync" |
||||
"time" |
||||
|
||||
"nhooyr.io/websocket" |
||||
) |
||||
|
||||
// New returns a net.Conn wrapper around c,
|
||||
// using c to send and receive binary messages with
|
||||
// chunks of bytes with no defined framing, effectively
|
||||
// discarding all WebSocket-level message framing.
|
||||
func New(c *websocket.Conn) net.Conn { |
||||
return &websocketConn{c: c} |
||||
} |
||||
|
||||
// websocketConn implements derp.Conn around a *websocket.Conn,
|
||||
// treating a websocket.Conn as a byte stream, ignoring the WebSocket
|
||||
// frame/message boundaries.
|
||||
type websocketConn struct { |
||||
c *websocket.Conn |
||||
|
||||
// rextra are extra bytes owned by the reader.
|
||||
rextra []byte |
||||
|
||||
mu sync.Mutex |
||||
rdeadline time.Time |
||||
cancelRead context.CancelFunc |
||||
} |
||||
|
||||
func (wc *websocketConn) LocalAddr() net.Addr { return addr{} } |
||||
func (wc *websocketConn) RemoteAddr() net.Addr { return addr{} } |
||||
|
||||
type addr struct{} |
||||
|
||||
func (addr) Network() string { return "websocket" } |
||||
func (addr) String() string { return "websocket" } |
||||
|
||||
func (wc *websocketConn) Read(p []byte) (n int, err error) { |
||||
// Drain any leftover from previously.
|
||||
n = copy(p, wc.rextra) |
||||
if n > 0 { |
||||
wc.rextra = wc.rextra[n:] |
||||
return n, nil |
||||
} |
||||
|
||||
var ctx context.Context |
||||
var cancel context.CancelFunc |
||||
|
||||
wc.mu.Lock() |
||||
if dl := wc.rdeadline; !dl.IsZero() { |
||||
ctx, cancel = context.WithDeadline(context.Background(), wc.rdeadline) |
||||
} else { |
||||
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(30*24*time.Hour)) |
||||
wc.rdeadline = time.Time{} |
||||
} |
||||
wc.cancelRead = cancel |
||||
wc.mu.Unlock() |
||||
defer cancel() |
||||
|
||||
_, buf, err := wc.c.Read(ctx) |
||||
n = copy(p, buf) |
||||
wc.rextra = buf[n:] |
||||
return n, err |
||||
} |
||||
|
||||
func (wc *websocketConn) Write(p []byte) (n int, err error) { |
||||
err = wc.c.Write(context.Background(), websocket.MessageBinary, p) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return len(p), nil |
||||
} |
||||
|
||||
func (wc *websocketConn) Close() error { return wc.c.Close(websocket.StatusNormalClosure, "close") } |
||||
|
||||
func (wc *websocketConn) SetDeadline(t time.Time) error { |
||||
wc.SetReadDeadline(t) |
||||
wc.SetWriteDeadline(t) |
||||
return nil |
||||
} |
||||
|
||||
func (wc *websocketConn) SetReadDeadline(t time.Time) error { |
||||
wc.mu.Lock() |
||||
defer wc.mu.Unlock() |
||||
if !t.IsZero() && (wc.rdeadline.IsZero() || t.Before(wc.rdeadline)) && wc.cancelRead != nil { |
||||
wc.cancelRead() |
||||
} |
||||
wc.rdeadline = t |
||||
return nil |
||||
} |
||||
|
||||
func (wc *websocketConn) SetWriteDeadline(t time.Time) error { |
||||
return nil |
||||
} |
||||
Loading…
Reference in new issue