diff --git a/backend/src/websocket/rdp.ts b/backend/src/websocket/rdp.ts index 614ac91..d174231 100644 --- a/backend/src/websocket/rdp.ts +++ b/backend/src/websocket/rdp.ts @@ -174,15 +174,18 @@ export async function rdpWebsocket(fastify: FastifyInstance) { dpi: '96', 'color-depth': '32', 'ignore-cert': 'true', - security: 'nla', // FreeRDP 3.x dropped 'any'; NLA is what Windows 11 uses by default + // 'nla' = Network Level Authentication (CredSSP). Required for Windows 11 22H2+ which + // mandates CredSSP v6. FreeRDP 3.x handles CredSSP v6 properly. + security: 'nla', 'disable-auth': 'false', 'enable-drive': 'false', 'create-drive-path': 'false', 'enable-printing': 'false', + 'disable-audio': 'true', 'disable-glyph-caching': 'false', - // disable-gfx removed: FreeRDP 3.x handles GFX pipeline correctly; disabling it caused crashes + 'disable-gfx': 'true', 'cert-tofu': 'true', - 'resize-method': 'display-update', + 'resize-method': 'reconnect', }; // Acknowledge whichever VERSION_x_y_z guacd advertises. @@ -194,24 +197,33 @@ export async function rdpWebsocket(fastify: FastifyInstance) { } } - // 3. Connect with values guacd requested - const argValues = argNames.map((name) => rdpParams[name] ?? ''); - guacd.write(buildInstruction('connect', ...argValues)); + // 3. Send size instruction so guacd populates client->info.optimal_width/height/resolution. + // Without this, guacd logs "0x0 at 0 DPI" and FreeRDP may use a zero-size display. + guacd.write(buildInstruction('size', rdpParams.width, rdpParams.height, rdpParams.dpi)); - // 4. Read ready instruction + // 4. Connect with values guacd requested + const argValues = argNames.map((name) => rdpParams[name] ?? ''); + const connectInstruction = buildInstruction('connect', ...argValues); + fastify.log.info( + { argNames, argValues, connectInstruction: connectInstruction.substring(0, 500) }, + 'RDP: sending connect instruction' + ); + guacd.write(connectInstruction); + + // 5. Read ready instruction const readyInstruction = await readInstruction(guacd, tcpBuf); fastify.log.info({ readyInstruction }, 'RDP: guacd ready instruction received'); if (readyInstruction[0] !== 'ready') { throw new Error(`guacd handshake failed: expected 'ready', got '${readyInstruction[0]}'`); } - // 5. Send the tunnel UUID as a Guacamole internal instruction. + // 6. Send the tunnel UUID as a Guacamole internal instruction. // WebSocketTunnel (1.5.0+) expects opcode "" (empty string) with the // UUID as the single argument: "0.,36.;" const guacdUUID = readyInstruction[1] ?? randomUUID(); socket.send(buildInstruction('', guacdUUID)); - // 6. Flush any buffered bytes that arrived after 'ready' + // 7. Flush any buffered bytes that arrived after 'ready' 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); @@ -230,7 +242,8 @@ export async function rdpWebsocket(fastify: FastifyInstance) { // --- Proxy mode --- guacd.on('data', (data: Buffer) => { const text = data.toString('utf8'); - fastify.log.debug({ guacdData: text.substring(0, 300) }, 'RDP: data from guacd'); + // Log all data during proxy phase at info level so we can see error instructions + fastify.log.info({ guacdData: text.substring(0, 500) }, 'RDP: data from guacd'); if (socket.readyState === WebSocket.OPEN) socket.send(text); }); diff --git a/docker-compose.yml b/docker-compose.yml index 266d30d..f2b87d1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,6 +23,7 @@ services: restart: unless-stopped environment: GUACD_LOG_LEVEL: debug + WLOG_LEVEL: "0" networks: - internal diff --git a/docker/guacd.Dockerfile b/docker/guacd.Dockerfile index 996785e..acb325c 100644 --- a/docker/guacd.Dockerfile +++ b/docker/guacd.Dockerfile @@ -5,11 +5,24 @@ # Why: The official guacamole/guacd image uses FreeRDP 2.x, which crashes # silently when connecting to Windows 11 22H2+ hosts due to NLA/CredSSP # cipher-suite changes introduced by Microsoft. FreeRDP 3.x fixes this. -# guacamole-server 1.6.0 (June 2025) has explicit FreeRDP 3.x support. # Ubuntu 24.04 ships freerdp3-dev (FreeRDP 3.5.1+) in its universe repo. # -# Note: FreeRDP 3.x support in guacamole is currently marked experimental -# for some features (RemoteApp), but basic RDP/NLA works correctly. +# Source: built from git main branch (post-1.6.0) to pick up FreeRDP 3.x +# crash fixes that landed after the June 2025 release tarball. +# +# Build notes: +# - CPPFLAGS=-Wno-error=deprecated-declarations suppresses build-time warnings from +# FreeRDP 3.x headers marking some fields/functions as deprecated; these are +# warnings only and do NOT affect runtime behavior. +# - CPPFLAGS=-DHAVE_FREERDP_VERIFYCERTIFICATEEX=1 fixes a macro name mismatch bug +# in guacamole-server 1.6.0: configure.ac's AC_CHECK_MEMBERS generates the macro +# HAVE_STRUCT_FREERDP_VERIFYCERTIFICATEEX (with STRUCT_ infix), but rdp.c checks +# HAVE_FREERDP_VERIFYCERTIFICATEEX (without STRUCT_ infix), so the check is always +# false. This means guacamole never registers the VerifyCertificateEx callback, which +# FreeRDP 3.x calls during TLS certificate verification. The NULL callback causes a +# silent connection drop ~430ms after keymap loading. Defining the macro manually +# forces rdp.c to register rdp_inst->VerifyCertificateEx (correct FreeRDP 3.x path) +# instead of the legacy rdp_inst->VerifyCertificate (padding in FreeRDP 3.x). FROM ubuntu:24.04 @@ -22,6 +35,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ build-essential \ ca-certificates \ curl \ + gdb \ + git \ freerdp3-dev \ libcairo2-dev \ libjpeg-turbo8-dev \ @@ -39,16 +54,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ pkgconf \ && rm -rf /var/lib/apt/lists/* -ARG GUACAMOLE_VERSION=1.6.0 - RUN FREERDP_PLUGIN_DIR=$(pkg-config --variable=libdir freerdp3 2>/dev/null)/freerdp3 \ - && echo "Building guacamole-server ${GUACAMOLE_VERSION} with FreeRDP plugin dir: ${FREERDP_PLUGIN_DIR}" \ - && curl -fsSL \ - "https://downloads.apache.org/guacamole/${GUACAMOLE_VERSION}/source/guacamole-server-${GUACAMOLE_VERSION}.tar.gz" \ - | tar -xzf - \ - && cd "guacamole-server-${GUACAMOLE_VERSION}" \ + && echo "Building guacamole-server (git main) with FreeRDP plugin dir: ${FREERDP_PLUGIN_DIR}" \ + && git clone --depth=1 https://github.com/apache/guacamole-server.git \ + && cd guacamole-server \ && autoreconf -fi \ - && CPPFLAGS="-Wno-error=deprecated-declarations" \ + && CPPFLAGS="-Wno-error=deprecated-declarations -DHAVE_FREERDP_VERIFYCERTIFICATEEX=1" \ + CFLAGS="-g -O0" \ ./configure \ --prefix=/usr \ --sysconfdir=/etc \ @@ -56,11 +68,11 @@ RUN FREERDP_PLUGIN_DIR=$(pkg-config --variable=libdir freerdp3 2>/dev/null)/free && make -j"$(nproc)" \ && make install \ && ldconfig \ - && cd / && rm -rf "guacamole-server-${GUACAMOLE_VERSION}" + && cd / && rm -rf guacamole-server # guacd log level is passed via -L flag; exposed as env var for docker-compose ENV GUACD_LOG_LEVEL=info EXPOSE 4822 -CMD sh -c "exec /usr/sbin/guacd -b 0.0.0.0 -f -L \"${GUACD_LOG_LEVEL}\"" +CMD sh -c "ulimit -c unlimited && exec /usr/sbin/guacd -b 0.0.0.0 -f -L \"${GUACD_LOG_LEVEL}\""