16 KiB
RemoteDesk 技术栈规划
当前决策:所有发布运行时使用 Rust;Agent 是服务端;原生客户端使用 winit、wgpu 和 egui;实时传输使用 str0m WebRTC。Go、Hysteria2、 Tauri 和 WebView 不属于目标组件。详见 ADR 0001。下文保留的旧技术栈内容仅用于 迁移对照,后续随代码迁移删除。
1. 混合架构
RemoteDesk Windows 客户端采用 Web UI 与 Rust 原生会话进程混合方案。JavaScript 负责管理界面和终端;Linux 桌面 WebRTC、Windows RDP 和 GPU 呈现均由 Rust 原生 helper 承载,原始视频像素不进入 WebView。
| 模块 | 技术栈 | 职责 |
|---|---|---|
| Client Shell | Tauri 2、Rust、Tokio | 应用生命周期、凭据、配对、窗口和 helper 管理 |
| Management UI | React、TypeScript、Vite | 主机、设置、诊断和会话工具栏 |
| Linux Terminal | xterm.js、DataChannel | ANSI/VT 终端和输入 |
| RDP Protocol Probe | Rust helper、IronRDP | 无凭据检查 DNS、TCP、X.224 与 RDP 安全协议协商 |
| Native RDP Viewer | Rust helper、IronRDP、winit、D3D11/DXGI | Windows RDP 原生窗口;优先使用 D3D11 swap chain CPU 上传,失败时回退 software framebuffer |
| Native Linux Video | Rust helper、gstreamer-rs、D3D11 | 默认 WebRTC、硬解和零拷贝呈现 |
| Linux Agent | Rust、Tokio、GStreamer、systemd | Wayland/Xorg/PTY 被控端 |
| 协议 | Protobuf、TLS WebSocket、WebRTC | 配对、会话和媒体数据 |
| Edge | Rust Rendezvous/Allocator、双 POP TURN、L4 Connector | CDN 优质骨干路径 |
| 数据 | SQLite、Windows Credential Manager | 配置和敏感凭据 |
2. 进程模型
remotedesk.exe (Tauri/Rust)
├── WebView2 main window
├── WebView2 terminal window
├── Rust session/auth/storage/edge services
│
├── remotedesk-rdp-session.exe
│ └── IronRDP protocol + TLS certificate probe (no credentials)
│
├── remotedesk-rdp-viewer.exe
│ └── IronRDP + winit native window + D3D11 CPU upload fallback
│
└── remotedesk-native-video.exe (default Linux desktop)
└── GStreamer WebRTC + selected D3D11 adapter
原生 helper 是独立进程,原因:
- Tauri/WebView2 与 winit/D3D11 不共享事件循环。
- RDP 或 GPU driver failure 不拖垮主界面。
- helper 可以拥有独立 GPU、DPI、Raw Input 和全屏生命周期。
- 主进程只交换状态和命令,不传输视频帧。
3. Tauri Client Shell
3.1 Rust Core
- Tauri 2 application runtime。
- Tokio 处理信令、配对、Edge 和 helper IPC。
- rusqlite 保存 HostProfile 与非敏感设置。
- Windows Credential Manager 保存密钥、密码和 Token。
- reqwest/rustls 调用边缘控制面。
- tracing 输出结构化日志。
Tauri command 使用最小 capability allowlist。JavaScript 不能直接读取 Credential Manager、任意文件、Agent 长期私钥。
3.2 React UI
- React + TypeScript + Vite。
- 状态按 Host/Profile/Session/Diagnostics 拆分。
- 图标使用成熟图标库,不在业务代码中手写 SVG。
- 页面资源全部本地打包,不加载 CDN 脚本或远端网页。
- Release 禁用开发者工具、任意导航和不必要 Web API。
UI 只接收结构化状态,不接收密码、长期私钥或媒体帧。
5. Linux Terminal
- xterm.js 负责 ANSI/VT、Unicode、中文宽字符、选择和滚动区。
- PTY 字节通过可靠 DataChannel 直接进入页面。
- 大量输出有有界缓存和背压。
- 多行粘贴前确认。
- Alternate Screen 与普通历史分离。
- JS 不解析或拼接远端 shell 命令,只呈现 PTY 字节。
xterm.js 性能通过高频输出 Spike 验证;若无法满足,再启用 Rust 原生终端 helper,不影响协议。
6. Native RDP Helpers
6.1 当前实现
- Rust stable。
remotedesk-rdp-session.exe:从有界 stdin JSON 接收目标地址,完成 DNS、TCP、X.224、RDP Negotiation Confirm 和无凭据 TLS 证书交换,不接收账号密码,也不进入 CredSSP/NLA。remotedesk-rdp-viewer.exe:使用 IronRDP 与 winit 建立单屏或全部本地显示器原生会话;账号和密码只在 helper 的本地遮罩提示中输入。- viewer 的桌面帧留在原生进程,不进入 React、Tauri IPC 或浏览器。
- 当前 viewer 优先报告
d3d11_cpu_upload,并通过独立能力d3d11_dirty_rect_upload声明局部上传;D3D11/DXGI 创建失败时报告software_framebuffer。单屏支持窗口尺寸驱动的动态分辨率,全部本地显示器使用 Win32 geometry 和 RDPEDISP 多 monitor layout,自定义显示器子集仍切换系统mstsc。
6.2 目标渲染路径
渲染路径:
IronRDP dirty rect
-> D3D11 upload/merge
-> GPU texture
-> native swap chain
IronRDP 关键能力不足时,主程序启动 mstsc 回退;mstsc 不伪装成内嵌原生 helper。
当前已完成 D3D11 device、持久 CPU framebuffer 与完整 GPU frame texture、dirty region 紧凑像素转换、多个待呈现 dirty rect 合并、局部 UpdateSubresource 和 swap chain 呈现。为保证 discard swap chain 下未变化区域正确,每次呈现仍从完整 frame texture 复制到 back buffer。因此该路径继续报告 d3d11_cpu_upload,不得报告硬件解码或零拷贝。
6.3 Helper IPC
主程序与 helper 使用 Windows Named Pipe:
- Pipe 使用当前用户 SID 的严格 ACL。
- RDP viewer 启动时命令行只接收随机 pipe name;一次性 32 字节 bootstrap key 只进入目标子进程环境并在读取后清除。
- 控制服务校验 pipe API 返回的客户端 PID、进程创建时间、实际可执行文件路径和 SHA-256 build hash,再完成 HMAC-SHA256 challenge。
- viewer 严格校验版本化配置后返回载荷 SHA-256 与 challenge 绑定的 HMAC ACK;服务端验证版本、摘要和 MAC 后才确认配置已接受。
- 连接、challenge/auth、配置帧和 ACK 共用有界绝对 deadline;非阻塞分段读取支持正常短读,但不会因已连接 helper 停滞而永久占用握手线程。
- viewer 启动后立即加入设置
KILL_ON_JOB_CLOSE的独立 Job Object;控制服务退出或握手失败时由 Job 终止遗留进程。 - 当前 RDP 管道发送证书指纹、opaque 凭据引用和连接参数,密码仍由 viewer 直接从 Credential Manager 读取;Session Ticket 后续复用同一有界协议。
- IPC 只传控制、状态、指标和输入命令,不传视频 frame。
- helper crash 后主程序显示结构化错误并可重启。
7. Native Linux Video Helper
这是 Linux 桌面默认客户端媒体进程,负责 WebRTC、DataChannel、Raw Input、硬件解码和呈现。默认选择窗口显示器所属 Adapter,用户也可手动指定。
当前 helper 的 Windows 源码除 D3D11 device 和 H.264 decoder allocation probe 外,还提供有界本地文件解码及原生窗口播放入口。它使用启用 DXVA/hardware transforms 且绑定 D3D11 device manager 的 Media Foundation Source Reader,将 H.264 MP4/M4V/MOV 请求为 NV12;输出必须是同一 device 上的 IMFDXGIBuffer texture,系统内存 buffer 会失败。播放器跟踪媒体类型的可见尺寸和变化标志,按 100 ns 时间戳调度 texture subresource,以同 device ID3D11VideoProcessor 转换和缩放到 BGRA swap chain,黑边由 render target 清除,过程中不映射 CPU frame。该链路提交真实压缩样本并实际调用窗口呈现,但在 ETW/GPUView 尚未证明 video decode engine 和无隐藏 copy 前仍报告 hardware_decode_verified=false、zero_copy_verified=false。
Linux 桌面控制 helper 的 Windows 模块还将远端 H.264 RTP 按 RFC 6184 重组为有界 Annex-B AU,覆盖单 NAL、STAP-A 和 FU-A。跨帧序号中断或本地一槽邮箱积压会清除参考链、请求 PLI 并等待新关键帧;重连时销毁旧解码器。AU 优先以 MFVideoFormat_H264_ES 输入 D3D11-aware H.264 MFT,只接受同一 device 的 NV12 IMFDXGIBuffer,再复用 VideoProcessor/swap-chain 模式呈现到 Linux 桌面窗口。第一帧呈现后,协议 minor 9 在完整 zlib 帧 ACK 后确认 H.264-only;随后每个 sequence 只在 D3D11 呈现成功后 ACK,Agent 才捕获下一帧,不再执行同帧 zlib 编码和传输。初始化、解码、媒体、sender/encoder 或 ACK 失败时恢复 zlib 软件 surface。该远程链路尚未编译和实机验证,不能据此打开 linux.native_video。
管线:
gstreamer-rs webrtcbin
-> D3D11 hardware decoder on selected adapter
-> GstD3D11Memory
-> d3d11videosink
-> native HWND
decoder、sink 和 swap chain 必须位于同一 Adapter;验证到 CPU map 或跨 Adapter copy 时,required_end_to_end 协商失败。该 helper 与 RDP helper 共享 WindowHost、GPU selection、IPC 和诊断 crate,但不共享协议 Adapter。
8. Linux Agent
agent/
└── crates/
├── agentd
├── desktop-session
├── shell-session
├── agent-core
├── agent-media
├── wayland-backend
├── xorg-backend
└── platform-linux
主要技术:Tokio、Axum/rustls、zbus/ashpd、gstreamer-rs、x11rb、rustix/nix、prost、rusqlite 和 tracing。
Wayland 使用 Portal/PipeWire/libei;Xorg 默认使用 XComposite/DRI3/XDamage/XRandR/XTest,XShm 仅兼容模式;终端使用 PAM/PTY。unsafe FFI 必须隔离在专用 crate。
Linux 视频内存策略:
preferred:
PipeWire memory:DMABuf
-> GPU color convert/scale
-> VA-API or NVENC hardware encoder
-> RTP
explicit compatibility fallback:
PipeWire MemPtr / XShm
-> CPU or GPU upload
-> encoder
-> RTP
Agent 媒体核心只传递带所有权和 fence 的 surface handle,不提供接收原始 RGBA Vec<u8> 的通用接口。禁止用 appsink 拉取原始帧再送入编码器。每种 GStreamer/驱动组合必须通过 tracer、perf 和 GPU 工具证明没有 CPU map/copy 后,才能把会话标记为 zero_copy。
Agent 默认从 DMA-BUF/DRI3 元数据解析 DRM render node,并在同一设备上选择 VA-API/NVENC video processor 和编码器。服务端手动 GPU 只有在能直接导入该 surface 时可用;跨 GPU 选择不满足严格模式。
9. 协议与 Edge
- Protobuf/Buf 定义共享消息契约。
- Rust 端使用 prost,Web UI 使用生成的 TypeScript codec。
- TLS WebSocket 负责配对和信令。
- WebRTC RTP/RTCP 负责媒体和质量反馈。
- IronRDP 原生会话把 RDP
NetworkCharacteristicsResult中服务器测得的平均 RTT、基础 RTT 和带宽提升为脱敏诊断事件;不以帧间隔推算网络时延。 - IronRDP 图形事件记录网络 PDU 在本地 active stage 的处理与 framebuffer 像素转换耗时;本地事件保持空值,且该指标不代表服务端编码或 GPU 视频解码。
- DataChannel 负责输入、终端、控制、剪贴板和文件。
- 智能比较 Direct、Single Edge 和 Dual Edge。
- Linux WebRTC 使用双 POP TURN;Windows RDP 使用透明 L4 Connector。
TURN credentials、SDP 和 Session Ticket 通过受限 Named Pipe 交给 native-video helper;长期设备私钥、Agent 授权和短期会话材料均不进入 JavaScript。
10. GPU 选择
配置模式:
inherit global
default
manual(adapter identity)
- RDP helper:默认和手动均可精确控制 DXGI Adapter。
- Linux native default:选择窗口显示器所属 Adapter,使 decoder、sink 和输出保持同 GPU。
- Linux manual:native-video helper 使用指定 LUID;窗口不在该 Adapter 的 output 上时严格模式拒绝跨 GPU copy。
- 指定 GPU 缺失时本次回退默认,不修改用户配置。
- 诊断展示 requested/effective GPU、decoder、driver 和 copy path。
- 零拷贝策略为默认
required_end_to_end或显式compatibility;前者要求 Agent/client 两端同时验证通过。
11. 分辨率实现
- Tauri/React:提供自动跟随、原始、固定预设和自定义尺寸菜单,保存主机级策略。
- IronRDP helper:把尺寸请求映射到 Display Control Dynamic Monitor Layout;不支持时仅在确认后重连。
- Linux Agent:使用 VA-API/CUDA 等 GPU video processor/scaler 和 DMA-BUF caps 在编码前缩放,不使用可能落入 CPU raw frame 的通用
videoscale,也不改变 Wayland/Xorg 实体显示模式。 - Native renderer:按收到的实际编码尺寸重建呈现资源,保持等比缩放并正确换算黑边输入坐标。
- Protobuf:传递 capability、request/applied、generation 和 source/encoded size,防止乱序 resize 覆盖新状态。
自动跟随窗口必须防抖;固定尺寸默认允许弱网临时下调,也可由用户锁定。所有模式都受双方最大纹理尺寸、编码器像素率和资源预算限制。
12. 仓库结构
RemoteDesk/
├── Cargo.toml
├── rust-toolchain.toml
├── client/
│ ├── src-tauri/
│ ├── web/
│ └── helpers/
│ ├── rdp-session/
│ └── native-video/
├── agent/
├── shared/
│ ├── protocol/
│ ├── session/
│ ├── helper-ipc/
│ └── diagnostics/
├── services/
│ ├── rendezvous/
│ └── relay-allocator/
├── protocol/
├── packaging/
└── tests/
13. 构建
Rust:
- Cargo workspace、
Cargo.lock、rust-toolchain.toml。 - rustfmt、Clippy、cargo-deny 和 cargo-audit。
Web:
- pnpm lockfile。
- TypeScript strict mode。
- ESLint、Vitest 和 Playwright。
- Vite 构建为本地静态资源。
Windows:
- Tauri/WiX MSI。
- 安装主程序、native helpers 和必要 GStreamer runtime。
- WebView2 Evergreen Runtime 作为前置条件。
- 主程序、helper 和安装包全部签名。
Linux:
- Cargo + 发行版 GStreamer/PipeWire/PAM/X11/libei。
- DEB/RPM 与 systemd units。
14. 测试
| 层级 | 工具 |
|---|---|
| Rust | cargo test、proptest |
| React/TypeScript | Vitest、Testing Library |
| WebRTC 页面 | Playwright + synthetic MediaStream |
| RDP | IronRDP + Windows VM |
| GPU | GPUView/ETW、GStreamer tracer |
| Agent | Cargo integration、真实 Wayland/Xorg |
| Edge | coturn、多 POP/netem |
| 端到端 | pytest + 虚拟化测试机 |
Web 视频测试必须证明 frame 不经过 JS/Canvas:页面不注册逐帧像素处理,Chromium media internals 显示硬解路径,CPU/拷贝指标符合基线。Linux Agent 同时使用 GStreamer tracer、perf 和驱动工具验证 DMA-BUF 未被 CPU map;仅看到 Video Encode 引擎占用不能作为零拷贝证据。
15. 安全
- WebView 使用严格 CSP:本地资源、必要的 media/connect source,禁止任意导航。
- Tauri capability 按窗口最小授权。
- Release 禁用 devtools 和远端脚本。
- Web 页面不获取长期私钥或 RDP 密码。
- Native helper IPC 使用 Named Pipe ACL、一次性 token 和进程验证。
- TURN/SDP/session material 限时并在窗口关闭后销毁。
- 依赖生成 SBOM,npm 与 Cargo 同时审计。
16. 许可证
- Tauri、React、xterm.js、IronRDP 和 Rust crates 进入许可证 allowlist。
- WebView2 Runtime 遵循 Microsoft 分发条款。
- GStreamer 插件逐项审计,不默认捆绑 GPL x264。
- 每个安装包附带第三方许可证和机器可读 SBOM。
17. M0 门槛
- Native-video helper 证明 D3D11 decoder 到 d3d11videosink 无 CPU/cross-GPU copy。
- Linux 输入、DataChannel 和系统组合键回退正确。
- xterm.js 通过中文、全屏 TUI 和高频输出测试。
- Native RDP helper 的 IronRDP/NLA/D3D11 通过。
- Named Pipe IPC 的 ACL、崩溃和重启通过。
- 默认显示器 GPU 与手动 GPU 的 native-video 零拷贝路径通过。
- Direct/Single/Dual Edge 路径通过。
- Wayland DMA-BUF 和 Xorg DRI3 到硬件编码器的零拷贝路径通过;任一端回退时默认拒绝。
18. 明确不做
- 不把视频 frame 通过 Tauri IPC 传给页面。
- 不用 Canvas/WebGL/WebGPU 逐帧绘制 Linux 远程视频。
- 不在 Web UI 中实现 RDP 协议。
- 不从零实现 RDP、WebRTC、TURN、编解码或密码学。
- 不把 mstsc 回退描述为内嵌 RDP。
该方案用 Web 平台降低管理 UI 和终端开发成本,用 Rust 原生 helper 承载所有默认桌面视频、RDP、精确 GPU、Raw Input 和驱动恢复能力。