-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathderp.go
More file actions
127 lines (115 loc) · 3.6 KB
/
derp.go
File metadata and controls
127 lines (115 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package tailnet
import (
"bufio"
"context"
"log"
"net/http"
"strings"
"sync"
"github.com/coder/websocket"
"tailscale.com/derp/derpserver"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
)
// NewDERPServer creates an embedded DERP relay server.
func NewDERPServer() *derpserver.Server {
logf := func(format string, args ...any) {
log.Printf("[derp-server] "+format, args...)
}
return derpserver.New(key.NewNode(), logf)
}
// DERPHandler returns an HTTP handler for the DERP server with WebSocket support.
// Mount at /derp on the chi router.
func DERPHandler(srv *derpserver.Server) (http.Handler, func()) {
baseHandler := derpserver.Handler(srv)
return WithWebsocketSupport(srv, baseHandler)
}
// WithWebsocketSupport upgrades WebSocket connections with the "derp" subprotocol
// and passes them to the DERP server. Falls back to the base handler for non-WS.
// Adapted from Coder's tailnet/derp.go.
func WithWebsocketSupport(s *derpserver.Server, base http.Handler) (http.Handler, func()) {
var mu sync.Mutex
var wg sync.WaitGroup
ctx, cancel := context.WithCancel(context.Background())
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
up := strings.ToLower(r.Header.Get("Upgrade"))
if up != "websocket" || !strings.Contains(r.Header.Get("Sec-Websocket-Protocol"), "derp") {
base.ServeHTTP(w, r)
return
}
mu.Lock()
if ctx.Err() != nil {
mu.Unlock()
return
}
wg.Add(1)
mu.Unlock()
defer wg.Done()
c, err := websocket.Accept(w, r, &websocket.AcceptOptions{
Subprotocols: []string{"derp"},
OriginPatterns: []string{"*"},
CompressionMode: websocket.CompressionDisabled,
})
if err != nil {
log.Printf("derp websocket accept error: remote=%s err=%v", r.RemoteAddr, err)
return
}
defer c.Close(websocket.StatusInternalError, "closing")
if c.Subprotocol() != "derp" {
c.Close(websocket.StatusPolicyViolation, "client must speak the derp subprotocol")
return
}
log.Printf("derp websocket accepted: remote=%s subproto=%s", r.RemoteAddr, c.Subprotocol())
wc := websocket.NetConn(ctx, c, websocket.MessageBinary)
brw := bufio.NewReadWriter(bufio.NewReader(wc), bufio.NewWriter(wc))
s.Accept(ctx, wc, brw, r.RemoteAddr)
log.Printf("derp session ended: remote=%s", r.RemoteAddr)
}), func() {
cancel()
mu.Lock()
wg.Wait()
mu.Unlock()
}
}
// NewDERPMap creates a relay-only DERPMap. STUN is disabled on the relay
// node (STUNPort=-1) to prevent STUN latency from competing with the
// DERP relay for preferred_derp selection. Use WithSTUNNode to add a
// co-located STUN node in the same region.
func NewDERPMap(hostname string, port int, insecure bool) *tailcfg.DERPMap {
return &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
1: {
RegionID: 1,
RegionCode: "cw",
RegionName: "Codewire Embedded",
Nodes: []*tailcfg.DERPNode{
{
Name: "1a",
RegionID: 1,
HostName: hostname,
DERPPort: port,
STUNPort: -1,
InsecureForTests: insecure,
},
},
},
},
}
}
// WithSTUNNode adds a STUNOnly node to region 1. This keeps STUN and
// DERP in the same region so preferred_derp always stays on the relay.
// The STUN node can be on a different host (e.g. a NodePort IP) from
// the DERP relay (e.g. behind a TCP LB).
func WithSTUNNode(dm *tailcfg.DERPMap, stunHost string, stunPort int) {
region := dm.Regions[1]
if region == nil {
return
}
region.Nodes = append(region.Nodes, &tailcfg.DERPNode{
Name: "1s",
RegionID: 1,
HostName: stunHost,
STUNOnly: true,
STUNPort: stunPort,
})
}