960598f504
guacamole-common-js 不会向 guacd 发送 select/connect;改为 Nexus 用 rdp_hosts 凭据完成握手后再桥接 WebSocket,消除 protocol violation。 Co-authored-by: Cursor <cursoragent@cursor.com>
85 lines
2.5 KiB
Python
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
|