fix(rdp): send client capability instructions before CONNECT handshake

ROOT CAUSE: guacd showed "User resolution is 0x0 at 0 DPI" and
immediately killed every RDP connection.

The Guacamole protocol requires five client capability instructions
(size, audio, video, image, timezone) BETWEEN receiving 'args' and
sending 'connect'. Our handshake skipped all five and jumped straight
to CONNECT. guacd never received the display dimensions, defaulted to
0x0, and terminated the connection.

Now sends the complete handshake:
  select → (receive args) → size → audio → video → image → timezone → connect

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell 2026-03-14 06:32:36 -04:00
parent 34bea52e0b
commit 1bf225ae27

View File

@ -64,11 +64,31 @@ export class GuacamoleService {
}
this.logger.log(`guacd expects ${argNames.length} args: ${argNames.join(', ')}`);
// Phase 2: CONNECT — send values in the exact order guacd expects
// Phase 2: Client capability instructions — MUST be sent before CONNECT.
// The Guacamole protocol requires: size, audio, video, image, timezone
// between receiving 'args' and sending 'connect'. Without these, guacd
// sees 0x0 resolution and immediately kills the connection.
const width = String(params.width);
const height = String(params.height);
const dpi = String(params.dpi || 96);
socket.write(this.encode('size', width, height, dpi));
socket.write(this.encode('audio'));
socket.write(this.encode('video'));
socket.write(this.encode('image', 'image/png', 'image/jpeg', 'image/webp'));
let tz: string;
try {
tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
} catch {
tz = 'UTC';
}
socket.write(this.encode('timezone', tz));
// Phase 3: CONNECT — send values in the exact order guacd expects
const connectInstruction = this.buildConnectInstruction(params, argNames);
this.logger.log(
`Sending CONNECT: host=${params.hostname}:${params.port} user=${params.username} domain=${params.domain || '(none)'} ` +
`security=${params.security || 'any'} size=${params.width}x${params.height} ignoreCert=${params.ignoreCert !== false}`,
`security=${params.security || 'any'} size=${width}x${height}@${dpi}dpi ignoreCert=${params.ignoreCert !== false}`,
);
socket.write(connectInstruction);