-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrelay.go
More file actions
145 lines (118 loc) · 3.05 KB
/
Copy pathrelay.go
File metadata and controls
145 lines (118 loc) · 3.05 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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package main
import (
"encoding/binary"
"log/slog"
"sync"
"time"
"github.com/coder/websocket"
)
const (
relayByteRateWindow = 60 * time.Second
relayByteRateMax = 10 * 1024 * 1024
)
type relayNodeState struct {
peerNodeIDs []int
byteCount int
windowStart time.Time
}
var relayRouter struct {
mu sync.RWMutex
byNode map[int]*relayNodeState
}
func init() {
relayRouter.byNode = make(map[int]*relayNodeState)
}
func registerRelayBinding(sourceNodeID, peerNodeID int) bool {
// Validate both nodes are connected
nodeRegistry.mu.RLock()
sourceNode := nodeRegistry.byID[sourceNodeID]
peerNode := nodeRegistry.byID[peerNodeID]
nodeRegistry.mu.RUnlock()
if sourceNode == nil || peerNode == nil {
return false
}
relayRouter.mu.Lock()
defer relayRouter.mu.Unlock()
state, ok := relayRouter.byNode[sourceNodeID]
if !ok {
state = &relayNodeState{windowStart: time.Now()}
relayRouter.byNode[sourceNodeID] = state
}
for _, id := range state.peerNodeIDs {
if id == peerNodeID {
return true
}
}
state.peerNodeIDs = append(state.peerNodeIDs, peerNodeID)
slog.Info("relay bound", "source", sourceNodeID, "peer", peerNodeID)
return true
}
func unregisterRelayBinding(sourceNodeID, peerNodeID int) {
relayRouter.mu.Lock()
defer relayRouter.mu.Unlock()
state, ok := relayRouter.byNode[sourceNodeID]
if !ok {
return
}
for i, id := range state.peerNodeIDs {
if id == peerNodeID {
state.peerNodeIDs = append(state.peerNodeIDs[:i], state.peerNodeIDs[i+1:]...)
break
}
}
if len(state.peerNodeIDs) == 0 {
delete(relayRouter.byNode, sourceNodeID)
}
}
func cleanupRelayBindings(nodeID int) {
relayRouter.mu.Lock()
defer relayRouter.mu.Unlock()
delete(relayRouter.byNode, nodeID)
}
// handleRelayPacket routes a binary frame from sourceNodeID to the destination.
// Binary frame format: [4-byte destNodeID][raw WireGuard packet]
// Note: the relay layer does not authenticate inner packet content — WireGuard's
// own Noise protocol handles peer authentication at the tunnel layer.
func handleRelayPacket(sourceNodeID int, raw []byte) {
if len(raw) < 5 || sourceNodeID < 0 {
return
}
destNodeID := int(binary.BigEndian.Uint32(raw[0:4]))
relayRouter.mu.Lock()
state, ok := relayRouter.byNode[sourceNodeID]
if !ok {
relayRouter.mu.Unlock()
return
}
now := time.Now()
if now.Sub(state.windowStart) > relayByteRateWindow {
state.windowStart = now
state.byteCount = 0
}
state.byteCount += len(raw)
if state.byteCount > relayByteRateMax {
relayRouter.mu.Unlock()
return
}
bound := false
for _, id := range state.peerNodeIDs {
if id == destNodeID {
bound = true
break
}
}
relayRouter.mu.Unlock()
if !bound {
return
}
nodeRegistry.mu.RLock()
destNode := nodeRegistry.byID[destNodeID]
nodeRegistry.mu.RUnlock()
if destNode == nil {
return
}
header := make([]byte, 4)
binary.BigEndian.PutUint32(header, uint32(sourceNodeID)) // #nosec G115 — node IDs are small positive integers from SQLite autoincrement
packet := append(header, raw[4:]...)
destNode.conn.safeWrite(websocket.MessageBinary, packet)
}