The mod_websocket continuation-frame patch accumulated fragments in an
unbounded dataBuffer with no size limit, fragment count limit, or
timeout. A buggy or malicious client sending endless continuation
frames without FIN could exhaust memory and degrade the entire server.
Add a configurable websocket_max_message_size option (default 2 MB)
that closes the connection with code 1009 ("Message too big") and
resets the buffer when exceeded. Also add wss-perf-analysis.md
documenting the WebSocket performance and drop investigation.
Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/8>
70 lines
2.1 KiB
Diff
70 lines
2.1 KiB
Diff
--- a/plugins/mod_websocket.lua 2026-07-15 09:48:22.424559760 +0200
|
|
+++ b/plugins/mod_websocket.lua 2026-07-16 12:00:00.000000000 +0200
|
|
@@ -199,9 +199,6 @@
|
|
end
|
|
|
|
-- Other (XMPP-specific) validity checks
|
|
- if not frame.FIN then
|
|
- return false, 1003, "Continuation frames are not supported, RFC 7395 3.3.3";
|
|
- end
|
|
if opcode == 0x01 and frame.data and frame.data:byte(1, 1) ~= 60 then
|
|
return false, 1007, "Invalid payload start character, RFC 7395 3.3.3";
|
|
end
|
|
@@ -249,6 +246,9 @@
|
|
end
|
|
end
|
|
|
|
+ local max_message_size = module:get_option_number("websocket_max_message_size", 2 * 1024 * 1024);
|
|
+ local dataBufferLen = 0;
|
|
+ local dataBuffer;
|
|
local function handle_frame(frame)
|
|
module:log("debug", "Websocket received frame: opcode=%0x, %i bytes", frame.opcode, #frame.data);
|
|
|
|
@@ -266,12 +266,44 @@
|
|
return "";
|
|
elseif opcode == 0xA then -- Pong frame, MAY be sent unsolicited, eg as keepalive
|
|
return "";
|
|
- elseif opcode ~= 0x1 then -- Not text frame (which is all we support)
|
|
+ end
|
|
+
|
|
+ -- Text (0x1) and continuation (0x0) frames: accumulate fragments
|
|
+ if opcode == 0x0 and not dataBuffer then
|
|
+ return false, 1002, "Unexpected continuation frame";
|
|
+ end
|
|
+ if opcode == 0x1 and dataBuffer then
|
|
+ return false, 1002, "Continuation frame expected";
|
|
+ end
|
|
+
|
|
+ if opcode == 0x0 then -- Continuation frame
|
|
+ dataBufferLen = dataBufferLen + #frame.data;
|
|
+ if dataBufferLen > max_message_size then
|
|
+ dataBuffer = nil;
|
|
+ dataBufferLen = 0;
|
|
+ return false, 1009, "Message too big";
|
|
+ end
|
|
+ dataBuffer[#dataBuffer+1] = frame.data;
|
|
+ elseif opcode == 0x1 then -- Text frame
|
|
+ dataBufferLen = #frame.data;
|
|
+ if dataBufferLen > max_message_size then
|
|
+ dataBuffer = nil;
|
|
+ dataBufferLen = 0;
|
|
+ return false, 1009, "Message too big";
|
|
+ end
|
|
+ dataBuffer = { frame.data };
|
|
+ else
|
|
log("warn", "Received frame with unsupported opcode %i", opcode);
|
|
return "";
|
|
end
|
|
|
|
- return frame.data;
|
|
+ if frame.FIN then
|
|
+ local data = t_concat(dataBuffer, "");
|
|
+ dataBuffer = nil;
|
|
+ dataBufferLen = 0;
|
|
+ return data;
|
|
+ end
|
|
+ return "";
|
|
end
|
|
|
|
conn:setlistener(c2s_listener);
|