Files
Nexus/server/infrastructure/guacamole/ws_tunnel.py
T
Nexus Agent 960598f504 fix(rdp): 服务端 guacd 握手替代透明 TCP 桥接
guacamole-common-js 不会向 guacd 发送 select/connect;改为 Nexus 用 rdp_hosts 凭据完成握手后再桥接 WebSocket,消除 protocol violation。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 21:33:01 +08:00

85 lines
2.5 KiB
Python

"""Guacamole WebSocket tunnel: handshake with guacd then relay (not raw TCP bridge)."""
from __future__ import annotations
import asyncio
import logging
from fastapi import WebSocket, WebSocketDisconnect
from server.infrastructure.guacamole.protocol import (
GuacdStream,
guacd_rdp_handshake,
is_internal_instruction,
tunnel_open_instruction,
)
logger = logging.getLogger("nexus.guacamole.ws_tunnel")
async def run_guacamole_rdp_tunnel(
websocket: WebSocket,
guacd_host: str,
guacd_port: int,
connect_params: dict[str, str],
*,
width: int = 1024,
height: int = 768,
) -> None:
"""Handshake with guacd using server-side credentials, then bridge WS ↔ guacd."""
reader, writer = await asyncio.open_connection(guacd_host, guacd_port)
stream = GuacdStream(reader)
try:
conn_id = await guacd_rdp_handshake(
stream, writer, connect_params, width=width, height=height,
)
await websocket.send_text(tunnel_open_instruction(conn_id))
except Exception as exc:
logger.warning("guacd handshake failed: %s", exc)
writer.close()
try:
await writer.wait_closed()
except Exception:
pass
await websocket.close(code=1011, reason=str(exc)[:120])
return
async def ws_to_guacd() -> None:
try:
while True:
data = await websocket.receive_text()
if is_internal_instruction(data):
if "ping" in data:
await websocket.send_text(data)
continue
writer.write(data.encode("utf-8"))
await writer.drain()
except WebSocketDisconnect:
pass
except Exception as exc:
logger.debug("RDP WS→guacd ended: %s", exc)
async def guacd_to_ws() -> None:
try:
while True:
chunk = await stream.read_some()
if not chunk:
break
await websocket.send_text(chunk.decode("utf-8", errors="replace"))
except Exception as exc:
logger.debug("RDP guacd→WS ended: %s", exc)
ws_task = asyncio.create_task(ws_to_guacd())
guacd_task = asyncio.create_task(guacd_to_ws())
try:
await asyncio.wait([ws_task, guacd_task], return_when=asyncio.FIRST_COMPLETED)
finally:
for task in (ws_task, guacd_task):
task.cancel()
writer.close()
try:
await writer.wait_closed()
except Exception:
pass