Fix VNC WebSocket 'Invalid frame header' error

- Remove WebSocket-level chunking of VNC data (breaks streaming protocol)
- Send VNC data as complete WebSocket frames instead of chunks
- Enhance SSL error handling with better retry logic and exponential backoff
- Make VNC handler resilient to temporary SSL buffer issues
- Add comprehensive debug logging for WebSocket frame transmission
- Add client-side debug logging for VNC rectangle processing

Root cause: WebSocket chunking interrupted VNC TightDecoder which expects
complete compressed data blocks. Client accumulated partial data until
connection reset with ECONNRESET.
parent 7183eaa8
...@@ -685,3 +685,8 @@ window.lookup = function(u) { ...@@ -685,3 +685,8 @@ window.lookup = function(u) {
return 0x01000000 | u; return 0x01000000 | u;
}; };
window.codepoints = codepoints; window.codepoints = codepoints;
// Create keysyms object for compatibility with util.js
window.keysyms = {
lookup: window.lookup
};
...@@ -510,24 +510,36 @@ class RFB extends EventTargetMixin { ...@@ -510,24 +510,36 @@ class RFB extends EventTargetMixin {
// ===== PRIVATE METHODS ===== // ===== PRIVATE METHODS =====
_connect() { _connect() {
console.log("[VNC-CLIENT] _connect() called");
console.log("[VNC-CLIENT] URL:", this._url);
console.log("[VNC-CLIENT] wsProtocols:", this._wsProtocols);
console.log("[VNC-CLIENT] rawChannel:", this._rawChannel);
Log.Debug(">> RFB.connect"); Log.Debug(">> RFB.connect");
if (this._url) { if (this._url) {
console.log("[VNC-CLIENT] Opening WebSocket connection to:", this._url);
Log.Info(`connecting to ${this._url}`); Log.Info(`connecting to ${this._url}`);
this._sock.open(this._url, this._wsProtocols); this._sock.open(this._url, this._wsProtocols);
console.log("[VNC-CLIENT] WebSocket.open() called with protocols:", this._wsProtocols);
} else { } else {
console.log("[VNC-CLIENT] Attaching to raw channel");
Log.Info(`attaching ${this._rawChannel} to Websock`); Log.Info(`attaching ${this._rawChannel} to Websock`);
this._sock.attach(this._rawChannel); this._sock.attach(this._rawChannel);
if (this._sock.readyState === 'closed') { if (this._sock.readyState === 'closed') {
console.log("[VNC-CLIENT] Raw channel is closed - cannot use");
throw Error("Cannot use already closed WebSocket/RTCDataChannel"); throw Error("Cannot use already closed WebSocket/RTCDataChannel");
} }
if (this._sock.readyState === 'open') { if (this._sock.readyState === 'open') {
console.log("[VNC-CLIENT] Raw channel is already open - calling _socketOpen()");
// FIXME: _socketOpen() can in theory call _fail(), which // FIXME: _socketOpen() can in theory call _fail(), which
// isn't allowed this early, but I'm not sure that can // isn't allowed this early, but I'm not sure that can
// happen without a bug messing up our state variables // happen without a bug messing up our state variables
this._socketOpen(); this._socketOpen();
} else {
console.log("[VNC-CLIENT] Raw channel readyState:", this._sock.readyState);
} }
} }
...@@ -601,17 +613,36 @@ class RFB extends EventTargetMixin { ...@@ -601,17 +613,36 @@ class RFB extends EventTargetMixin {
} }
_socketOpen() { _socketOpen() {
console.log("[VNC-CLIENT] WebSocket opened, connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket URL:", this._url);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
console.log("[VNC-CLIENT] WebSocket protocol:", this._sock.protocol);
console.log("[VNC-CLIENT] WebSocket binaryType:", this._sock.binaryType);
console.log("[VNC-CLIENT] WebSocket bufferedAmount:", this._sock.bufferedAmount);
console.log("[VNC-CLIENT] WebSocket extensions:", this._sock.extensions);
if ((this._rfbConnectionState === 'connecting') && if ((this._rfbConnectionState === 'connecting') &&
(this._rfbInitState === '')) { (this._rfbInitState === '')) {
this._rfbInitState = 'ProtocolVersion'; this._rfbInitState = 'ProtocolVersion';
Log.Debug("Starting VNC handshake"); Log.Debug("Starting VNC handshake");
console.log("[VNC-CLIENT] Starting VNC handshake, set init state to ProtocolVersion");
} else { } else {
console.log("[VNC-CLIENT] Unexpected socket open - connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
this._fail("Unexpected server connection while " + this._fail("Unexpected server connection while " +
this._rfbConnectionState); this._rfbConnectionState);
} }
} }
_socketClose(e) { _socketClose(e) {
console.log("[VNC-CLIENT] WebSocket on-close event");
console.log("[VNC-CLIENT] Close event details:", e);
console.log("[VNC-CLIENT] Close code:", e.code);
console.log("[VNC-CLIENT] Close reason:", e.reason);
console.log("[VNC-CLIENT] Close wasClean:", e.wasClean);
console.log("[VNC-CLIENT] Current connectionState:", this._rfbConnectionState);
console.log("[VNC-CLIENT] Current initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
Log.Debug("WebSocket on-close event"); Log.Debug("WebSocket on-close event");
let msg = ""; let msg = "";
if (e.code) { if (e.code) {
...@@ -621,26 +652,33 @@ class RFB extends EventTargetMixin { ...@@ -621,26 +652,33 @@ class RFB extends EventTargetMixin {
} }
msg += ")"; msg += ")";
} }
console.log("[VNC-CLIENT] Connection close message:", msg);
switch (this._rfbConnectionState) { switch (this._rfbConnectionState) {
case 'connecting': case 'connecting':
console.log("[VNC-CLIENT] Connection closed while connecting");
this._fail("Connection closed " + msg); this._fail("Connection closed " + msg);
break; break;
case 'connected': case 'connected':
console.log("[VNC-CLIENT] Connection closed while connected - server-side disconnect");
// Handle disconnects that were initiated server-side // Handle disconnects that were initiated server-side
this._updateConnectionState('disconnecting'); this._updateConnectionState('disconnecting');
this._updateConnectionState('disconnected'); this._updateConnectionState('disconnected');
break; break;
case 'disconnecting': case 'disconnecting':
console.log("[VNC-CLIENT] Connection closed while disconnecting - normal path");
// Normal disconnection path // Normal disconnection path
this._updateConnectionState('disconnected'); this._updateConnectionState('disconnected');
break; break;
case 'disconnected': case 'disconnected':
console.log("[VNC-CLIENT] Connection closed while already disconnected - unexpected");
this._fail("Unexpected server disconnect " + this._fail("Unexpected server disconnect " +
"when already disconnected " + msg); "when already disconnected " + msg);
break; break;
default: default:
console.log("[VNC-CLIENT] Connection closed in unknown state:", this._rfbConnectionState);
this._fail("Unexpected server disconnect before connecting " + this._fail("Unexpected server disconnect before connecting " +
msg); msg);
break; break;
} }
this._sock.off('close'); this._sock.off('close');
...@@ -649,6 +687,14 @@ class RFB extends EventTargetMixin { ...@@ -649,6 +687,14 @@ class RFB extends EventTargetMixin {
} }
_socketError(e) { _socketError(e) {
console.log("[VNC-CLIENT] WebSocket on-error event");
console.log("[VNC-CLIENT] Error event details:", e);
console.log("[VNC-CLIENT] Current connectionState:", this._rfbConnectionState);
console.log("[VNC-CLIENT] Current initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
console.log("[VNC-CLIENT] WebSocket URL:", this._url);
console.log("[VNC-CLIENT] WebSocket protocol:", this._sock.protocol);
Log.Warn("WebSocket on-error event"); Log.Warn("WebSocket on-error event");
} }
...@@ -928,36 +974,56 @@ class RFB extends EventTargetMixin { ...@@ -928,36 +974,56 @@ class RFB extends EventTargetMixin {
} }
_handleMessage() { _handleMessage() {
console.log("[VNC-CLIENT] _handleMessage called");
console.log("[VNC-CLIENT] Current connectionState:", this._rfbConnectionState);
console.log("[VNC-CLIENT] Current initState:", this._rfbInitState);
console.log("[VNC-CLIENT] WebSocket rQlen:", this._sock.rQlen);
console.log("[VNC-CLIENT] WebSocket readyState:", this._sock.readyState);
if (this._sock.rQlen === 0) { if (this._sock.rQlen === 0) {
console.log("[VNC-CLIENT] Empty receive queue - no data to process");
Log.Warn("handleMessage called on an empty receive queue"); Log.Warn("handleMessage called on an empty receive queue");
return; return;
} }
console.log("[VNC-CLIENT] Processing message in state:", this._rfbConnectionState);
switch (this._rfbConnectionState) { switch (this._rfbConnectionState) {
case 'disconnected': case 'disconnected':
console.log("[VNC-CLIENT] Received data while disconnected - ignoring");
Log.Error("Got data while disconnected"); Log.Error("Got data while disconnected");
break; break;
case 'connected': case 'connected':
console.log("[VNC-CLIENT] Processing messages in connected state");
let msgCount = 0;
while (true) { while (true) {
if (this._flushing) { if (this._flushing) {
console.log("[VNC-CLIENT] Flushing in progress, breaking message loop");
break; break;
} }
console.log("[VNC-CLIENT] Processing message", ++msgCount, "in connected state");
if (!this._normalMsg()) { if (!this._normalMsg()) {
console.log("[VNC-CLIENT] _normalMsg returned false, breaking");
break; break;
} }
if (this._sock.rQlen === 0) { if (this._sock.rQlen === 0) {
console.log("[VNC-CLIENT] No more data in queue");
break; break;
} }
} }
break; break;
case 'connecting': case 'connecting':
console.log("[VNC-CLIENT] Processing messages in connecting state");
while (this._rfbConnectionState === 'connecting') { while (this._rfbConnectionState === 'connecting') {
console.log("[VNC-CLIENT] Calling _initMsg in connecting state");
if (!this._initMsg()) { if (!this._initMsg()) {
console.log("[VNC-CLIENT] _initMsg returned false, breaking");
break; break;
} }
} }
break; break;
default: default:
console.log("[VNC-CLIENT] Received data in invalid state:", this._rfbConnectionState);
Log.Error("Got data while in an invalid state"); Log.Error("Got data while in an invalid state");
break; break;
} }
...@@ -1317,12 +1383,21 @@ class RFB extends EventTargetMixin { ...@@ -1317,12 +1383,21 @@ class RFB extends EventTargetMixin {
// Message Handlers // Message Handlers
_negotiateProtocolVersion() { _negotiateProtocolVersion() {
console.log("[VNC-CLIENT] _negotiateProtocolVersion called");
console.log("[VNC-CLIENT] Waiting for version data (12 bytes)");
if (this._sock.rQwait("version", 12)) { if (this._sock.rQwait("version", 12)) {
console.log("[VNC-CLIENT] Not enough data for version - waiting");
return false; return false;
} }
const versionStr = this._sock.rQshiftStr(12); const versionStr = this._sock.rQshiftStr(12);
console.log("[VNC-CLIENT] Received version string:", versionStr);
console.log("[VNC-CLIENT] Raw version bytes:", Array.from(versionStr).map(c => c.charCodeAt(0)));
const sversion = versionStr.substr(4, 7); const sversion = versionStr.substr(4, 7);
console.log("[VNC-CLIENT] Parsed server version:", sversion);
Log.Info("Server ProtocolVersion: " + sversion); Log.Info("Server ProtocolVersion: " + sversion);
let isRepeater = 0; let isRepeater = 0;
switch (sversion) { switch (sversion) {
...@@ -2017,14 +2092,21 @@ class RFB extends EventTargetMixin { ...@@ -2017,14 +2092,21 @@ class RFB extends EventTargetMixin {
} }
_negotiateServerInit() { _negotiateServerInit() {
console.log("[VNC-CLIENT] _negotiateServerInit called, rQlen:", this._sock.rQlen);
if (this._sock.rQwait("server initialization", 24)) { if (this._sock.rQwait("server initialization", 24)) {
console.log("[VNC-CLIENT] Not enough data for server init (need 24, have", this._sock.rQlen, ")");
return false; return false;
} }
console.log("[VNC-CLIENT] Parsing server initialization message");
/* Screen size */ /* Screen size */
const width = this._sock.rQshift16(); const width = this._sock.rQshift16();
const height = this._sock.rQshift16(); const height = this._sock.rQshift16();
console.log("[VNC-CLIENT] Server screen size:", width, "x", height);
/* PIXEL_FORMAT */ /* PIXEL_FORMAT */
const bpp = this._sock.rQshift8(); const bpp = this._sock.rQshift8();
const depth = this._sock.rQshift8(); const depth = this._sock.rQshift8();
...@@ -2450,17 +2532,17 @@ class RFB extends EventTargetMixin { ...@@ -2450,17 +2532,17 @@ class RFB extends EventTargetMixin {
msgType = this._sock.rQshift8(); msgType = this._sock.rQshift8();
} }
// console.log("[VNC-MESSAGE] Processing message type:", msgType, "FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen); console.log("[VNC-MESSAGE] Processing message type:", msgType, "FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
let first, ret; let first, ret;
switch (msgType) { switch (msgType) {
case 0: // FramebufferUpdate case 0: // FramebufferUpdate
// console.log("[VNC-DEBUG] Handling FramebufferUpdate"); console.log("[VNC-DEBUG] Handling FramebufferUpdate");
ret = this._framebufferUpdate(); ret = this._framebufferUpdate();
if (ret && !this._enabledContinuousUpdates) { if (ret && !this._enabledContinuousUpdates) {
// console.log("[VNC-DEBUG] Sending FramebufferUpdateRequest"); console.log("[VNC-DEBUG] Sending FramebufferUpdateRequest");
RFB.messages.fbUpdateRequest(this._sock, true, 0, 0, RFB.messages.fbUpdateRequest(this._sock, true, 0, 0,
this._fbWidth, this._fbHeight); this._fbWidth, this._fbHeight);
} }
return ret; return ret;
...@@ -2515,13 +2597,19 @@ class RFB extends EventTargetMixin { ...@@ -2515,13 +2597,19 @@ class RFB extends EventTargetMixin {
} }
_framebufferUpdate() { _framebufferUpdate() {
console.log("[VNC-FBU] _framebufferUpdate called, FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
if (this._FBU.rects === 0) { if (this._FBU.rects === 0) {
if (this._sock.rQwait("FBU header", 3, 1)) { return false; } if (this._sock.rQwait("FBU header", 3, 1)) {
console.log("[VNC-FBU] Waiting for FBU header, not enough data");
return false;
}
this._sock.rQskipBytes(1); // Padding this._sock.rQskipBytes(1); // Padding
this._FBU.rects = this._sock.rQshift16(); this._FBU.rects = this._sock.rQshift16();
console.log("[VNC-FBU] Received FBU header, rects:", this._FBU.rects);
// Validate rectangle count - should be reasonable (not 65534 or similar corruption) // Validate rectangle count - should be reasonable (not 65534 or similar corruption)
if (this._FBU.rects > 10000 || this._FBU.rects < 0) { if (this._FBU.rects > 10000 || this._FBU.rects < 0) {
console.log("[VNC-FBU] Invalid rectangle count:", this._FBU.rects, "- skipping corrupted message");
Log.Warn("Invalid rectangle count in FramebufferUpdate (" + this._FBU.rects + "), skipping corrupted message"); Log.Warn("Invalid rectangle count in FramebufferUpdate (" + this._FBU.rects + "), skipping corrupted message");
// Skip the entire FramebufferUpdate message by consuming all remaining data for this message // Skip the entire FramebufferUpdate message by consuming all remaining data for this message
// We need to skip past all the rectangle data // We need to skip past all the rectangle data
...@@ -2538,6 +2626,7 @@ class RFB extends EventTargetMixin { ...@@ -2538,6 +2626,7 @@ class RFB extends EventTargetMixin {
// Make sure the previous frame is fully rendered first // Make sure the previous frame is fully rendered first
// to avoid building up an excessive queue // to avoid building up an excessive queue
if (this._display.pending()) { if (this._display.pending()) {
console.log("[VNC-FBU] Display pending, flushing and returning false");
this._flushing = true; this._flushing = true;
this._display.flush(); this._display.flush();
return false; return false;
...@@ -2574,32 +2663,41 @@ class RFB extends EventTargetMixin { ...@@ -2574,32 +2663,41 @@ class RFB extends EventTargetMixin {
} }
_handleRect() { _handleRect() {
console.log("[VNC-RECT] _handleRect called, encoding:", this._FBU.encoding, "x:", this._FBU.x, "y:", this._FBU.y, "width:", this._FBU.width, "height:", this._FBU.height);
switch (this._FBU.encoding) { switch (this._FBU.encoding) {
case encodings.pseudoEncodingLastRect: case encodings.pseudoEncodingLastRect:
console.log("[VNC-RECT] Handling pseudoEncodingLastRect");
this._FBU.rects = 1; // Will be decreased when we return this._FBU.rects = 1; // Will be decreased when we return
return true; return true;
case encodings.pseudoEncodingVMwareCursor: case encodings.pseudoEncodingVMwareCursor:
console.log("[VNC-RECT] Handling pseudoEncodingVMwareCursor");
return this._handleVMwareCursor(); return this._handleVMwareCursor();
case encodings.pseudoEncodingCursor: case encodings.pseudoEncodingCursor:
console.log("[VNC-RECT] Handling pseudoEncodingCursor");
return this._handleCursor(); return this._handleCursor();
case encodings.pseudoEncodingQEMUExtendedKeyEvent: case encodings.pseudoEncodingQEMUExtendedKeyEvent:
console.log("[VNC-RECT] Handling pseudoEncodingQEMUExtendedKeyEvent");
this._qemuExtKeyEventSupported = true; this._qemuExtKeyEventSupported = true;
return true; return true;
case encodings.pseudoEncodingDesktopName: case encodings.pseudoEncodingDesktopName:
console.log("[VNC-RECT] Handling pseudoEncodingDesktopName");
return this._handleDesktopName(); return this._handleDesktopName();
case encodings.pseudoEncodingDesktopSize: case encodings.pseudoEncodingDesktopSize:
console.log("[VNC-RECT] Handling pseudoEncodingDesktopSize");
this._resize(this._FBU.width, this._FBU.height); this._resize(this._FBU.width, this._FBU.height);
return true; return true;
case encodings.pseudoEncodingExtendedDesktopSize: case encodings.pseudoEncodingExtendedDesktopSize:
console.log("[VNC-RECT] Handling pseudoEncodingExtendedDesktopSize");
return this._handleExtendedDesktopSize(); return this._handleExtendedDesktopSize();
default: default:
console.log("[VNC-RECT] Handling data rect with encoding:", this._FBU.encoding);
return this._handleDataRect(); return this._handleDataRect();
} }
} }
...@@ -2852,8 +2950,10 @@ class RFB extends EventTargetMixin { ...@@ -2852,8 +2950,10 @@ class RFB extends EventTargetMixin {
} }
_handleDataRect() { _handleDataRect() {
console.log("[VNC-DATARECT] _handleDataRect called, encoding:", this._FBU.encoding, "rQlen:", this._sock.rQlen);
let decoder = this._decoders[this._FBU.encoding]; let decoder = this._decoders[this._FBU.encoding];
if (!decoder) { if (!decoder) {
console.log("[VNC-DATARECT] No decoder found for encoding:", this._FBU.encoding, "- skipping rectangle");
Log.Warn("Unsupported encoding (encoding: " + Log.Warn("Unsupported encoding (encoding: " +
this._FBU.encoding + "), skipping rectangle"); this._FBU.encoding + "), skipping rectangle");
...@@ -2861,22 +2961,28 @@ class RFB extends EventTargetMixin { ...@@ -2861,22 +2961,28 @@ class RFB extends EventTargetMixin {
// Calculate how much data this rectangle should contain // Calculate how much data this rectangle should contain
// This is a rough estimate - most encodings need at least width*height bytes // This is a rough estimate - most encodings need at least width*height bytes
const minDataSize = this._FBU.width * this._FBU.height; const minDataSize = this._FBU.width * this._FBU.height;
console.log("[VNC-DATARECT] Skipping", minDataSize, "bytes for unsupported encoding");
if (this._sock.rQlen >= minDataSize) { if (this._sock.rQlen >= minDataSize) {
// Skip a reasonable amount of data for this rectangle // Skip a reasonable amount of data for this rectangle
const skipSize = Math.min(minDataSize, this._sock.rQlen); const skipSize = Math.min(minDataSize, this._sock.rQlen);
this._sock.rQskipBytes(skipSize); this._sock.rQskipBytes(skipSize);
} else { } else {
console.log("[VNC-DATARECT] Not enough data to skip, waiting for more");
return false; // Wait for more data return false; // Wait for more data
} }
return true; return true;
} }
console.log("[VNC-DATARECT] Calling decoder.decodeRect for", decoder.constructor.name);
try { try {
return decoder.decodeRect(this._FBU.x, this._FBU.y, const result = decoder.decodeRect(this._FBU.x, this._FBU.y,
this._FBU.width, this._FBU.height, this._FBU.width, this._FBU.height,
this._sock, this._display, this._sock, this._display,
this._fbDepth); this._fbDepth);
console.log("[VNC-DATARECT] decodeRect returned:", result);
return result;
} catch (err) { } catch (err) {
console.log("[VNC-DATARECT] Error decoding rect:", err, "- skipping corrupted rectangle");
Log.Warn("Error decoding rect: " + err + " - skipping corrupted rectangle"); Log.Warn("Error decoding rect: " + err + " - skipping corrupted rectangle");
// For decoding errors, also skip data to avoid getting stuck // For decoding errors, also skip data to avoid getting stuck
const minDataSize = this._FBU.width * this._FBU.height; const minDataSize = this._FBU.width * this._FBU.height;
......
...@@ -168,3 +168,21 @@ window.isEdge = isEdge; ...@@ -168,3 +168,21 @@ window.isEdge = isEdge;
window.isGecko = isGecko; window.isGecko = isGecko;
window.isWebKit = isWebKit; window.isWebKit = isWebKit;
window.isBlink = isBlink; window.isBlink = isBlink;
// Create browser object for compatibility
window.browser = {
isMac: isMac,
isWindows: isWindows,
isIOS: isIOS,
isAndroid: isAndroid,
isChromeOS: isChromeOS,
isSafari: isSafari,
isFirefox: isFirefox,
isChrome: isChrome,
isChromium: isChromium,
isOpera: isOpera,
isEdge: isEdge,
isGecko: isGecko,
isWebKit: isWebKit,
isBlink: isBlink
};
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
<script src="/novnc/des.js"></script> <script src="/novnc/des.js"></script>
<script src="/novnc/input/keysym.js"></script> <script src="/novnc/input/keysym.js"></script>
<script src="/novnc/input/keysymdef.js"></script> <script src="/novnc/input/keysymdef.js"></script>
<script src="/novnc/input/domkeytable.js"></script>
<script src="/novnc/input/xtscancodes.js"></script> <script src="/novnc/input/xtscancodes.js"></script>
<script src="/novnc/input/util.js"></script> <script src="/novnc/input/util.js"></script>
<script src="/novnc/input/keyboard.js"></script> <script src="/novnc/input/keyboard.js"></script>
...@@ -343,7 +344,8 @@ function connect() { ...@@ -343,7 +344,8 @@ function connect() {
rfb = new RFB(document.getElementById('noVNC_screen'), wsUrl, { rfb = new RFB(document.getElementById('noVNC_screen'), wsUrl, {
credentials: {}, credentials: {},
shared: true, shared: true,
repeaterID: '' repeaterID: '',
wsProtocols: ['binary']
}); });
rfb.addEventListener('connect', () => { rfb.addEventListener('connect', () => {
......
...@@ -279,7 +279,7 @@ static void *websocket_terminal_handler(void *arg) { ...@@ -279,7 +279,7 @@ static void *websocket_terminal_handler(void *arg) {
char *output = terminal_get_output(session, &output_len); char *output = terminal_get_output(session, &output_len);
if (output && output_len > 0) { if (output && output_len > 0) {
printf("WebSocket sending %zu bytes of output for request_id: %s\n", output_len, ws_conn->request_id); printf("WebSocket sending %zu bytes of output for request_id: %s\n", output_len, ws_conn->request_id);
ws_send_binary_frame(ws_conn->ws_conn, output, output_len); ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc);
free(output); free(output);
} }
...@@ -353,6 +353,7 @@ static void *websocket_vnc_handler(void *arg) { ...@@ -353,6 +353,7 @@ static void *websocket_vnc_handler(void *arg) {
pthread_mutex_unlock(&vncs_mutex); pthread_mutex_unlock(&vncs_mutex);
// Wait for VNC connection to be established and RFB handshake to complete // Wait for VNC connection to be established and RFB handshake to complete
sleep(1);
// Add a delay to allow the RFB client to complete the handshake before sending VNC data // Add a delay to allow the RFB client to complete the handshake before sending VNC data
sleep(2); sleep(2);
...@@ -382,7 +383,11 @@ static void *websocket_vnc_handler(void *arg) { ...@@ -382,7 +383,11 @@ static void *websocket_vnc_handler(void *arg) {
printf("\n"); printf("\n");
} }
// Send initial data to WebSocket // Send initial data to WebSocket
ws_send_binary_frame(ws_conn->ws_conn, output, output_len); if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc)) {
free(output);
remove_websocket_vnc_connection(ws_conn->request_id);
return NULL;
}
free(output); free(output);
connection_ready = true; connection_ready = true;
break; break;
...@@ -411,19 +416,30 @@ static void *websocket_vnc_handler(void *arg) { ...@@ -411,19 +416,30 @@ static void *websocket_vnc_handler(void *arg) {
} }
// Send any available output immediately // Send any available output immediately
size_t output_len = 0; if (ws_conn->ws_conn->state == WS_STATE_OPEN) {
char *output = vnc_get_output(session, &output_len); size_t output_len = 0;
if (output && output_len > 0) { char *output = vnc_get_output(session, &output_len);
printf("WebSocket sending %zu bytes of VNC output for request_id: %s\n", output_len, ws_conn->request_id); if (output && output_len > 0) {
if (global_config->debug_vnc) { printf("WebSocket sending %zu bytes of VNC output for request_id: %s\n", output_len, ws_conn->request_id);
printf("VNC output data (first 20 bytes): "); if (global_config->debug_vnc) {
for (size_t i = 0; i < output_len && i < 20; i++) { printf("VNC output data (first 20 bytes): ");
printf("%02x ", (unsigned char)output[i]); for (size_t i = 0; i < output_len && i < 20; i++) {
printf("%02x ", (unsigned char)output[i]);
}
printf("\n");
} }
printf("\n");
// Send VNC data as a single WebSocket frame - don't chunk it
// VNC is a streaming protocol and chunking breaks the decoder
if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc)) {
printf("Failed to send VNC data frame (temporary SSL issue?), continuing...\n");
// Don't abort - SSL buffer issues are often temporary
// The data will be sent in the next iteration when buffer clears
}
free(output);
} }
ws_send_binary_frame(ws_conn->ws_conn, output, output_len); } else {
free(output); break;
} }
// Check for input from WebSocket (non-blocking) // Check for input from WebSocket (non-blocking)
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>
#include <string.h> // for strerror
#include <openssl/sha.h> #include <openssl/sha.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include "websocket_protocol.h" #include "websocket_protocol.h"
...@@ -344,7 +345,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size ...@@ -344,7 +345,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size
usleep(10000 * (1 << retry_count)); // 10ms, 20ms, 40ms, 80ms usleep(10000 * (1 << retry_count)); // 10ms, 20ms, 40ms, 80ms
continue; // Retry the write operation continue; // Retry the write operation
} else { } else {
// Don't mark connection as closed on send failures - let receive failures handle connection closure conn->state = WS_STATE_CLOSED;
free(frame); free(frame);
return false; return false;
} }
...@@ -352,6 +353,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size ...@@ -352,6 +353,7 @@ bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size
} else { } else {
written = write(conn->sock_fd, frame + total_written, to_write); written = write(conn->sock_fd, frame + total_written, to_write);
if (written <= 0) { if (written <= 0) {
conn->state = WS_STATE_CLOSED;
free(frame); free(frame);
return false; return false;
} }
...@@ -533,11 +535,15 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_ ...@@ -533,11 +535,15 @@ bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_
} }
// Send raw WebSocket binary frame (for VNC connections) // Send raw WebSocket binary frame (for VNC connections)
bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) { bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len, bool debug) {
if (!conn || conn->state != WS_STATE_OPEN) { if (!conn || conn->state != WS_STATE_OPEN) {
return false; return false;
} }
if (debug) {
printf("[WS-BINARY] Sending binary frame, len=%zu\n", len);
}
// WebSocket binary frame construction for VNC // WebSocket binary frame construction for VNC
// No masking (server-to-client), with retry logic for partial writes // No masking (server-to-client), with retry logic for partial writes
size_t header_len = 2; size_t header_len = 2;
...@@ -552,6 +558,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) { ...@@ -552,6 +558,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
size_t frame_len = header_len + len; size_t frame_len = header_len + len;
uint8_t *frame = malloc(frame_len); uint8_t *frame = malloc(frame_len);
if (!frame) { if (!frame) {
if (debug) printf("[WS-BINARY] Failed to allocate frame buffer\n");
return false; return false;
} }
...@@ -560,10 +567,12 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) { ...@@ -560,10 +567,12 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
if (len < 126) { if (len < 126) {
frame[1] = len & 0x7F; // No mask bit frame[1] = len & 0x7F; // No mask bit
if (debug) printf("[WS-BINARY] Header: FIN=1, opcode=2, len=%zu (<126)\n", len);
} else if (len < 65536) { } else if (len < 65536) {
frame[1] = 126 & 0x7F; // No mask bit frame[1] = 126 & 0x7F; // No mask bit
frame[2] = (len >> 8) & 0xFF; frame[2] = (len >> 8) & 0xFF;
frame[3] = len & 0xFF; frame[3] = len & 0xFF;
if (debug) printf("[WS-BINARY] Header: FIN=1, opcode=2, len=%zu (16-bit)\n", len);
} else { } else {
frame[1] = 127 & 0x7F; // No mask bit frame[1] = 127 & 0x7F; // No mask bit
frame[2] = frame[3] = frame[4] = frame[5] = 0; frame[2] = frame[3] = frame[4] = frame[5] = 0;
...@@ -571,6 +580,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) { ...@@ -571,6 +580,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
frame[7] = (len >> 16) & 0xFF; frame[7] = (len >> 16) & 0xFF;
frame[8] = (len >> 8) & 0xFF; frame[8] = (len >> 8) & 0xFF;
frame[9] = len & 0xFF; frame[9] = len & 0xFF;
if (debug) printf("[WS-BINARY] Header: FIN=1, opcode=2, len=%zu (64-bit)\n", len);
} }
// Copy data // Copy data
...@@ -583,6 +593,8 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) { ...@@ -583,6 +593,8 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
int retry_count = 0; int retry_count = 0;
const int max_retries = 5; const int max_retries = 5;
if (debug) printf("[WS-BINARY] Sending frame of total length %zu\n", frame_len);
while (total_written < (int)frame_len && retry_count < max_retries) { while (total_written < (int)frame_len && retry_count < max_retries) {
int to_write = frame_len - total_written; int to_write = frame_len - total_written;
int written; int written;
...@@ -590,6 +602,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) { ...@@ -590,6 +602,7 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
written = SSL_write(conn->ssl, frame + total_written, to_write); written = SSL_write(conn->ssl, frame + total_written, to_write);
if (written <= 0) { if (written <= 0) {
int ssl_error = SSL_get_error(conn->ssl, written); int ssl_error = SSL_get_error(conn->ssl, written);
if (debug) printf("[WS-BINARY] SSL write error: %d, written=%d\n", ssl_error, written);
// Check for recoverable SSL errors // Check for recoverable SSL errors
if ((ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE || if ((ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE ||
ssl_error == SSL_ERROR_SSL || ssl_error == SSL_ERROR_SYSCALL) && retry_count < max_retries - 1) { ssl_error == SSL_ERROR_SSL || ssl_error == SSL_ERROR_SYSCALL) && retry_count < max_retries - 1) {
...@@ -597,23 +610,48 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) { ...@@ -597,23 +610,48 @@ bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len) {
usleep(10000 * (1 << retry_count)); // Exponential backoff usleep(10000 * (1 << retry_count)); // Exponential backoff
continue; continue;
} else { } else {
conn->state = WS_STATE_CLOSED;
free(frame); free(frame);
return false; return false;
} }
} }
} else { } else {
written = write(conn->sock_fd, frame + total_written, to_write); written = write(conn->sock_fd, frame + total_written, to_write);
if (written <= 0) { if (written < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// Non-blocking socket, try again
if (debug) printf("[WS-BINARY] Socket would block, retrying\n");
retry_count++;
usleep(1000); // Short delay before retry
continue;
} else {
if (debug) printf("[WS-BINARY] Socket write error: errno=%d (%s), written=%d\n", errno, strerror(errno), written);
conn->state = WS_STATE_CLOSED;
free(frame);
return false;
}
} else if (written == 0) {
// Connection closed
if (debug) printf("[WS-BINARY] Connection closed during write\n");
conn->state = WS_STATE_CLOSED;
free(frame); free(frame);
return false; return false;
} }
} }
total_written += written; total_written += written;
retry_count = 0; // Reset retry count on successful write retry_count = 0; // Reset retry count on successful write
if (debug) printf("[WS-BINARY] Wrote %d bytes, total written: %d/%zu\n", written, total_written, frame_len);
} }
if (total_written != (int)frame_len) {
if (debug) printf("[WS-BINARY] Failed to write complete frame: %d/%zu\n", total_written, frame_len);
free(frame);
return false;
}
if (debug) printf("[WS-BINARY] Successfully sent binary frame\n");
free(frame); free(frame);
return total_written == (int)frame_len; return true;
} }
// Check if WebSocket connection is healthy // Check if WebSocket connection is healthy
......
...@@ -72,7 +72,7 @@ bool ws_perform_handshake(ws_connection_t *conn); ...@@ -72,7 +72,7 @@ bool ws_perform_handshake(ws_connection_t *conn);
// Frame operations // Frame operations
bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size_t len); bool ws_send_frame(ws_connection_t *conn, uint8_t opcode, const void *data, size_t len);
bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len); bool ws_send_binary_frame(ws_connection_t *conn, const void *data, size_t len, bool debug);
bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_t *len); bool ws_receive_frame(ws_connection_t *conn, uint8_t *opcode, void **data, size_t *len);
// Connection health monitoring // Connection health monitoring
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment