From 459674d2fd304fdad137839cd2177fa517721793 Mon Sep 17 00:00:00 2001 From: deadRabbit Date: Sun, 22 Feb 2026 14:24:28 +0100 Subject: [PATCH] RDP diagnostics: TCP pre-check, disable-gfx, better logging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add TCP connectivity pre-test before guacd handshake — gives a clear error message if the RDP host is unreachable from the Docker network - Add disable-gfx:true (disables GFX Pipeline Extension, known FreeRDP 2.x crash source on Windows 10/11) and cert-tofu:true - Log tcpBuf flush content and all guacd data at debug level so we can see exactly what guacd sends after the ready instruction Co-Authored-By: Claude Sonnet 4.6 --- backend/src/websocket/rdp.ts | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/backend/src/websocket/rdp.ts b/backend/src/websocket/rdp.ts index 904ae0e..02df589 100644 --- a/backend/src/websocket/rdp.ts +++ b/backend/src/websocket/rdp.ts @@ -128,7 +128,29 @@ export async function rdpWebsocket(fastify: FastifyInstance) { return; } - fastify.log.info({ host: conn.host, port: conn.port, user: conn.username }, 'RDP: starting guacd handshake'); + // Pre-check: verify the RDP server is TCP-reachable from this container. + // The backend is on the same Docker network as guacd, so same reachability. + try { + await new Promise((resolve, reject) => { + const test = createConnection(conn.port, conn.host); + const timer = setTimeout(() => { + test.destroy(); + reject(new Error(`Cannot reach ${conn.host}:${conn.port} — connection timed out`)); + }, 5000); + test.once('connect', () => { clearTimeout(timer); test.destroy(); resolve(); }); + test.once('error', (err) => { + clearTimeout(timer); + reject(new Error(`Cannot reach ${conn.host}:${conn.port} — ${err.message}`)); + }); + }); + } catch (err: unknown) { + const msg = err instanceof Error ? err.message : 'Cannot reach RDP host'; + fastify.log.warn({ host: conn.host, port: conn.port }, msg); + socket.close(1011, msg); + return; + } + + fastify.log.info({ host: conn.host, port: conn.port, user: conn.username }, 'RDP: TCP reachable, starting guacd handshake'); try { // 1. Select protocol @@ -158,6 +180,8 @@ export async function rdpWebsocket(fastify: FastifyInstance) { 'create-drive-path': 'false', 'enable-printing': 'false', 'disable-glyph-caching': 'true', + 'disable-gfx': 'true', // disable GFX Pipeline Extension (known FreeRDP 2.x crash source) + 'cert-tofu': 'true', // trust certificate on first use 'resize-method': 'display-update', }; @@ -188,8 +212,9 @@ export async function rdpWebsocket(fastify: FastifyInstance) { socket.send(buildInstruction('', guacdUUID)); // 6. Flush any buffered bytes that arrived after 'ready' - if (tcpBuf.value.length > 0 && socket.readyState === WebSocket.OPEN) { - socket.send(tcpBuf.value); + if (tcpBuf.value.length > 0) { + fastify.log.info({ flushed: tcpBuf.value.substring(0, 300) }, 'RDP: flushing buffered data after ready'); + if (socket.readyState === WebSocket.OPEN) socket.send(tcpBuf.value); tcpBuf.value = ''; } } catch (err: unknown) { @@ -205,10 +230,7 @@ export async function rdpWebsocket(fastify: FastifyInstance) { // --- Proxy mode --- guacd.on('data', (data: Buffer) => { const text = data.toString('utf8'); - // Log the first message from guacd (usually an error or first instruction) - if (text.startsWith('5.error') || text.startsWith('10.disconnect')) { - fastify.log.warn({ guacdMsg: text.substring(0, 200) }, 'RDP: guacd sent error/disconnect'); - } + fastify.log.debug({ guacdData: text.substring(0, 300) }, 'RDP: data from guacd'); if (socket.readyState === WebSocket.OPEN) socket.send(text); });