feat(multiplayer): bound control-plane websocket traffic

This commit is contained in:
Josh Creek
2026-09-01 19:25:34 +01:00
parent b110bfc5f7
commit 515b06d97c
3 changed files with 71 additions and 5 deletions
+31 -4
View File
@@ -19,9 +19,12 @@ import (
)
const (
webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
maxWebSocketFrame = 64 << 10
eventQueueCapacity = 32
webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
maxWebSocketFrame = 64 << 10
eventQueueCapacity = 32
webSocketIdleLimit = 2 * time.Minute
webSocketMessageLimit = 120
webSocketMessageWindow = time.Minute
)
// ControlPlaneEvent is the server-to-client envelope defined by the v1
@@ -139,7 +142,7 @@ func (s *Service) controlPlaneEvent(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusMethodNotAllowed, "method_not_allowed")
return
}
if !isWebSocketUpgrade(r) || !validWebSocketKey(r.Header.Get("Sec-WebSocket-Key")) {
if !isWebSocketUpgrade(r) || r.Header.Get("Sec-WebSocket-Version") != "13" || !validWebSocketKey(r.Header.Get("Sec-WebSocket-Key")) {
writeError(w, http.StatusBadRequest, "invalid_websocket_upgrade")
return
}
@@ -250,11 +253,20 @@ func validWebSocketKey(key string) bool {
func readWebSocketFrames(connection net.Conn, writeMu *sync.Mutex) {
reader := bufio.NewReader(connection)
windowStarted := time.Now()
messageCount := 0
for {
if err := connection.SetReadDeadline(time.Now().Add(webSocketIdleLimit)); err != nil {
return
}
opcode, _, err := readWebSocketFrame(reader)
if err != nil || opcode == 0x8 {
return
}
now := time.Now()
if !allowWebSocketMessage(now, &windowStarted, &messageCount) {
return
}
if opcode == 0x9 {
writeMu.Lock()
_ = writeWebSocketFrame(connection, 0xA, nil)
@@ -263,6 +275,21 @@ func readWebSocketFrames(connection net.Conn, writeMu *sync.Mutex) {
}
}
func allowWebSocketMessage(now time.Time, windowStarted *time.Time, count *int) bool {
if windowStarted == nil || count == nil || now.IsZero() {
return false
}
if !now.Before(windowStarted.Add(webSocketMessageWindow)) {
*windowStarted = now
*count = 0
}
if *count >= webSocketMessageLimit {
return false
}
*count++
return true
}
func readWebSocketFrame(reader *bufio.Reader) (byte, []byte, error) {
first, err := reader.ReadByte()
if err != nil {