feat: 添加 RustDesk 协议支持和项目文档

- 新增 RustDesk 模块,支持与 RustDesk 客户端连接
  - 实现会合服务器协议和 P2P 连接
  - 支持 NaCl 加密和密钥交换
  - 添加视频帧和 HID 事件适配器
- 添加 Protobuf 协议定义 (message.proto, rendezvous.proto)
- 新增完整项目文档
  - 各功能模块文档 (video, hid, msd, otg, webrtc 等)
  - hwcodec 和 RustDesk 协议技术报告
  - 系统架构和技术栈文档
- 更新 Web 前端 RustDesk 配置界面和 API
This commit is contained in:
mofeng-git
2025-12-31 18:59:52 +08:00
parent 61323a7664
commit a8a3b6c66b
57 changed files with 20830 additions and 0 deletions

View File

@@ -0,0 +1,550 @@
# hwcodec 技术架构报告
## 1. 项目概述
hwcodec 是一个基于 FFmpeg 的硬件视频编解码库,来源于 RustDesk 项目并针对 One-KVM 进行了定制优化。该库提供跨平台的 GPU 加速视频编解码能力,支持多个 GPU 厂商和多种编码标准。
### 1.1 项目位置
```
libs/hwcodec/
├── src/ # Rust 源代码
├── cpp/ # C++ 源代码
├── externals/ # 外部依赖 (SDK)
├── dev/ # 开发工具
└── examples/ # 示例程序
```
### 1.2 核心特性
- **多编解码格式支持**: H.264, H.265 (HEVC), VP8, VP9, AV1, MJPEG
- **硬件加速**: NVENC/NVDEC, AMF, Intel QSV/MFX, VAAPI, RKMPP, V4L2 M2M, VideoToolbox
- **跨平台**: Windows, Linux, macOS, Android, iOS
- **低延迟优化**: 专为实时流媒体场景设计
- **Rust/C++ 混合架构**: Rust 提供安全的上层 APIC++ 实现底层编解码逻辑
## 2. 架构设计
### 2.1 整体架构图
```
┌─────────────────────────────────────────────────────────────┐
│ Rust API Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ ffmpeg_ram │ │ vram │ │ mux │ │
│ │ module │ │ module │ │ module │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
├─────────┼────────────────┼───────────────────┼──────────────┤
│ │ │ │ │
│ │ FFI Bindings (bindgen) │ │
│ ▼ ▼ ▼ │
├─────────────────────────────────────────────────────────────┤
│ C++ Core Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ ffmpeg_ram │ │ ffmpeg_vram │ │ mux.cpp │ │
│ │ encode/ │ │ encode/ │ │ │ │
│ │ decode │ │ decode │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
├─────────┼────────────────┼───────────────────┼──────────────┤
│ │ │ │ │
│ └────────────────┴───────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ FFmpeg Libraries │ │
│ │ libavcodec │ libavutil │ libavformat │ libswscale │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │
├──────────────────────────┼──────────────────────────────────┤
│ Hardware Acceleration Backends │
│ ┌────────┐ ┌─────┐ ┌─────┐ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ NVENC │ │ AMF │ │ MFX │ │ VAAPI │ │ RKMPP │ │V4L2M2M│ │
│ └────────┘ └─────┘ └─────┘ └───────┘ └───────┘ └───────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.2 模块职责
| 模块 | 职责 | 关键文件 |
|------|------|----------|
| `ffmpeg_ram` | 基于 RAM 的软件/硬件编解码 | `src/ffmpeg_ram/` |
| `vram` | GPU 显存直接编解码 (Windows) | `src/vram/` |
| `mux` | 视频混流 (MP4/MKV) | `src/mux.rs` |
| `common` | 公共定义和 GPU 检测 | `src/common.rs` |
| `ffmpeg` | FFmpeg 日志和初始化 | `src/ffmpeg.rs` |
## 3. 模块详细分析
### 3.1 库入口 (lib.rs)
```rust
// libs/hwcodec/src/lib.rs
pub mod common;
pub mod ffmpeg;
pub mod ffmpeg_ram;
pub mod mux;
#[cfg(all(windows, feature = "vram"))]
pub mod vram;
#[cfg(target_os = "android")]
pub mod android;
```
**功能**:
- 导出所有子模块
- 提供 C 日志回调函数 `hwcodec_log`
- 条件编译: `vram` 模块仅在 Windows + vram feature 启用时编译
### 3.2 公共模块 (common.rs)
**核心类型**:
```rust
pub enum Driver {
NV, // NVIDIA
AMF, // AMD
MFX, // Intel
FFMPEG, // 软件编码
}
```
**GPU 检测函数**:
| 平台 | 检测函数 | 检测方式 |
|------|----------|----------|
| Linux | `linux_support_nv()` | 加载 CUDA/NVENC 动态库 |
| Linux | `linux_support_amd()` | 检查 `libamfrt64.so.1` |
| Linux | `linux_support_intel()` | 检查 `libvpl.so`/`libmfx.so` |
| Linux | `linux_support_rkmpp()` | 检查 `/dev/mpp_service` |
| Linux | `linux_support_v4l2m2m()` | 检查 `/dev/video*` 设备 |
| macOS | `get_video_toolbox_codec_support()` | 调用 VideoToolbox API |
| Windows | 通过 VRAM 模块检测 | 查询 D3D11 设备 |
### 3.3 FFmpeg RAM 编码模块
#### 3.3.1 Rust 层 (src/ffmpeg_ram/)
**CodecInfo 结构体**:
```rust
pub struct CodecInfo {
pub name: String, // 编码器名称如 "h264_nvenc"
pub mc_name: Option<String>, // MediaCodec 名称 (Android)
pub format: DataFormat, // H264/H265/VP8/VP9/AV1/MJPEG
pub priority: i32, // 优先级 (Best=0, Good=1, Normal=2, Soft=3, Bad=4)
pub hwdevice: AVHWDeviceType, // 硬件设备类型
}
```
**EncodeContext 结构体**:
```rust
pub struct EncodeContext {
pub name: String, // 编码器名称
pub width: i32, // 视频宽度
pub height: i32, // 视频高度
pub pixfmt: AVPixelFormat, // 像素格式 (NV12/YUV420P)
pub align: i32, // 内存对齐
pub fps: i32, // 帧率
pub gop: i32, // GOP 大小
pub rc: RateControl, // 码率控制模式
pub quality: Quality, // 质量级别
pub kbs: i32, // 目标码率 (kbps)
pub q: i32, // 量化参数
pub thread_count: i32, // 线程数
}
```
**Encoder 类**:
```rust
pub struct Encoder {
codec: *mut c_void, // C++ 编码器指针
frames: *mut Vec<EncodeFrame>, // 编码输出帧
pub ctx: EncodeContext,
pub linesize: Vec<i32>, // 行大小
pub offset: Vec<i32>, // 平面偏移
pub length: i32, // 总数据长度
}
```
**核心方法**:
| 方法 | 功能 |
|------|------|
| `Encoder::new()` | 创建编码器实例 |
| `Encoder::encode()` | 编码一帧 YUV 数据 |
| `Encoder::set_bitrate()` | 动态调整码率 |
| `Encoder::request_keyframe()` | 请求下一帧为关键帧 |
| `Encoder::available_encoders()` | 检测系统可用编码器 |
#### 3.3.2 C++ 层 (cpp/ffmpeg_ram/)
**FFmpegRamEncoder 类** (ffmpeg_ram_encode.cpp:97-420):
```cpp
class FFmpegRamEncoder {
AVCodecContext *c_ = NULL; // FFmpeg 编码上下文
AVFrame *frame_ = NULL; // 输入帧
AVPacket *pkt_ = NULL; // 编码输出包
AVBufferRef *hw_device_ctx_; // 硬件设备上下文
AVFrame *hw_frame_ = NULL; // 硬件帧
bool force_keyframe_ = false; // 强制关键帧标志
// 主要方法
bool init(int *linesize, int *offset, int *length);
int encode(const uint8_t *data, int length, const void *obj, uint64_t ms);
int do_encode(AVFrame *frame, const void *obj, int64_t ms);
int set_hwframe_ctx(); // 设置硬件帧上下文
};
```
**编码流程**:
```
输入 YUV 数据
fill_frame() - 填充 AVFrame 数据指针
├──▶ (软件编码) 直接使用 frame_
└──▶ (硬件编码) av_hwframe_transfer_data() 传输到 GPU
使用 hw_frame_
avcodec_send_frame() - 发送帧到编码器
avcodec_receive_packet() - 获取编码数据
callback() - 回调输出
```
### 3.4 FFmpeg RAM 解码模块
**Decoder 类**:
```rust
pub struct Decoder {
codec: *mut c_void,
frames: *mut Vec<DecodeFrame>,
pub ctx: DecodeContext,
}
pub struct DecodeFrame {
pub pixfmt: AVPixelFormat,
pub width: i32,
pub height: i32,
pub data: Vec<Vec<u8>>, // Y, U, V 平面数据
pub linesize: Vec<i32>,
pub key: bool,
}
```
**C++ 实现** (ffmpeg_ram_decode.cpp):
```cpp
class FFmpegRamDecoder {
AVCodecContext *c_ = NULL;
AVBufferRef *hw_device_ctx_ = NULL;
AVFrame *sw_frame_ = NULL; // 软件帧 (用于硬件→软件转换)
AVFrame *frame_ = NULL; // 解码输出帧
AVPacket *pkt_ = NULL;
bool hwaccel_ = true;
int do_decode(const void *obj);
};
```
**解码流程**:
```
输入编码数据
avcodec_send_packet() - 发送数据到解码器
avcodec_receive_frame() - 获取解码帧
├──▶ (软件解码) 直接使用 frame_
└──▶ (硬件解码) av_hwframe_transfer_data()
sw_frame_ (GPU → CPU)
callback() - 回调输出
```
## 4. 硬件加速支持
### 4.1 支持的硬件加速后端
| 后端 | 厂商 | 平台 | 编码器名称 |
|------|------|------|-----------|
| NVENC | NVIDIA | Windows/Linux | h264_nvenc, hevc_nvenc |
| AMF | AMD | Windows/Linux | h264_amf, hevc_amf |
| QSV | Intel | Windows | h264_qsv, hevc_qsv |
| VAAPI | 通用 | Linux | h264_vaapi, hevc_vaapi, vp8_vaapi, vp9_vaapi |
| RKMPP | Rockchip | Linux | h264_rkmpp, hevc_rkmpp |
| V4L2 M2M | ARM SoC | Linux | h264_v4l2m2m, hevc_v4l2m2m |
| VideoToolbox | Apple | macOS/iOS | hevc_videotoolbox |
| MediaCodec | Google | Android | h264_mediacodec, hevc_mediacodec |
### 4.2 硬件检测逻辑 (Linux)
```cpp
// libs/hwcodec/cpp/common/platform/linux/linux.cpp
// NVIDIA 检测 - 加载 CUDA 和 NVENC 动态库
int linux_support_nv() {
CudaFunctions *cuda_dl = NULL;
NvencFunctions *nvenc_dl = NULL;
CuvidFunctions *cvdl = NULL;
load_driver(&cuda_dl, &nvenc_dl, &cvdl);
// 成功加载则返回 0
}
// AMD 检测 - 检查 AMF 运行时库
int linux_support_amd() {
void *handle = dlopen("libamfrt64.so.1", RTLD_LAZY);
// 成功加载则返回 0
}
// Intel 检测 - 检查 VPL/MFX 库
int linux_support_intel() {
const char *libs[] = {"libvpl.so", "libmfx.so", ...};
// 任一成功加载则返回 0
}
// Rockchip MPP 检测 - 检查设备节点
int linux_support_rkmpp() {
if (access("/dev/mpp_service", F_OK) == 0) return 0;
if (access("/dev/rga", F_OK) == 0) return 0;
return -1;
}
// V4L2 M2M 检测 - 检查视频设备
int linux_support_v4l2m2m() {
const char *devices[] = {"/dev/video10", "/dev/video11", ...};
// 任一设备可打开则返回 0
}
```
### 4.3 编码器优先级系统
```rust
pub enum Priority {
Best = 0, // 最高优先级 (硬件加速)
Good = 1, // 良好 (VAAPI, 部分硬件)
Normal = 2, // 普通
Soft = 3, // 软件编码
Bad = 4, // 最低优先级
}
```
**优先级分配**:
| 编码器 | 优先级 |
|--------|--------|
| h264_nvenc, hevc_nvenc | Best (0) |
| h264_amf, hevc_amf | Best (0) |
| h264_qsv, hevc_qsv | Best (0) |
| h264_rkmpp, hevc_rkmpp | Best (0) |
| h264_vaapi, hevc_vaapi | Good (1) |
| h264_v4l2m2m, hevc_v4l2m2m | Good (1) |
| h264 (x264), hevc (x265) | Soft (3) |
### 4.4 低延迟优化配置
```cpp
// libs/hwcodec/cpp/common/util.cpp
bool set_lantency_free(void *priv_data, const std::string &name) {
// NVENC: 禁用延迟缓冲
if (name.find("nvenc") != std::string::npos) {
av_opt_set(priv_data, "delay", "0", 0);
}
// AMF: 设置查询超时
if (name.find("amf") != std::string::npos) {
av_opt_set(priv_data, "query_timeout", "1000", 0);
}
// QSV/VAAPI: 设置异步深度为 1
if (name.find("qsv") != std::string::npos ||
name.find("vaapi") != std::string::npos) {
av_opt_set(priv_data, "async_depth", "1", 0);
}
// VideoToolbox: 实时模式
if (name.find("videotoolbox") != std::string::npos) {
av_opt_set_int(priv_data, "realtime", 1, 0);
av_opt_set_int(priv_data, "prio_speed", 1, 0);
}
// libvpx: 实时模式
if (name.find("libvpx") != std::string::npos) {
av_opt_set(priv_data, "deadline", "realtime", 0);
av_opt_set_int(priv_data, "cpu-used", 6, 0);
av_opt_set_int(priv_data, "lag-in-frames", 0, 0);
}
return true;
}
```
## 5. 混流模块 (Mux)
### 5.1 功能概述
混流模块提供将编码后的视频流写入容器格式 (MP4/MKV) 的功能。
### 5.2 Rust API
```rust
// libs/hwcodec/src/mux.rs
pub struct MuxContext {
pub filename: String, // 输出文件名
pub width: usize, // 视频宽度
pub height: usize, // 视频高度
pub is265: bool, // 是否为 H.265
pub framerate: usize, // 帧率
}
pub struct Muxer {
inner: *mut c_void, // C++ Muxer 指针
pub ctx: MuxContext,
start: Instant, // 开始时间
}
impl Muxer {
pub fn new(ctx: MuxContext) -> Result<Self, ()>;
pub fn write_video(&mut self, data: &[u8], key: bool) -> Result<(), i32>;
pub fn write_tail(&mut self) -> Result<(), i32>;
}
```
### 5.3 C++ 实现
```cpp
// libs/hwcodec/cpp/mux/mux.cpp
class Muxer {
OutputStream video_st; // 视频流
AVFormatContext *oc = NULL; // 格式上下文
int framerate;
int64_t start_ms; // 起始时间戳
int64_t last_pts; // 上一帧 PTS
int got_first; // 是否收到第一帧
bool init(const char *filename, int width, int height,
int is265, int framerate);
int write_video_frame(const uint8_t *data, int len,
int64_t pts_ms, int key);
};
```
**写入流程**:
```
write_video_frame()
├── 检查是否为关键帧 (第一帧必须是关键帧)
├── 计算 PTS (相对于 start_ms)
├── 填充 AVPacket
├── av_packet_rescale_ts() (ms → stream timebase)
└── av_write_frame() → 写入文件
```
## 6. 构建系统
### 6.1 Cargo.toml 配置
```toml
[package]
name = "hwcodec"
version = "0.7.1"
[features]
default = []
vram = [] # GPU VRAM 直接编解码 (Windows only)
[dependencies]
log = "0.4"
serde_derive = "1.0"
serde = "1.0"
serde_json = "1.0"
[build-dependencies]
cc = "1.0" # C++ 编译
bindgen = "0.59" # FFI 绑定生成
```
### 6.2 构建流程 (build.rs)
```
build.rs
├── build_common()
│ ├── 生成 common_ffi.rs (bindgen)
│ ├── 编译平台相关 C++ 代码
│ └── 链接系统库 (d3d11, dxgi, stdc++)
├── ffmpeg::build_ffmpeg()
│ ├── 生成 ffmpeg_ffi.rs
│ ├── 链接 FFmpeg 库 (VCPKG 或 pkg-config)
│ ├── build_ffmpeg_ram()
│ │ └── 编译 ffmpeg_ram_encode.cpp, ffmpeg_ram_decode.cpp
│ ├── build_ffmpeg_vram() [vram feature]
│ │ └── 编译 ffmpeg_vram_encode.cpp, ffmpeg_vram_decode.cpp
│ └── build_mux()
│ └── 编译 mux.cpp
└── sdk::build_sdk() [Windows + vram feature]
├── build_nv() - NVIDIA SDK
├── build_amf() - AMD AMF
└── build_mfx() - Intel MFX
```
### 6.3 FFmpeg 链接方式
| 方式 | 平台 | 条件 |
|------|------|------|
| VCPKG 静态链接 | 跨平台 | 设置 `VCPKG_ROOT` 环境变量 |
| pkg-config 动态链接 | Linux | 默认方式 |
## 7. 外部依赖
### 7.1 SDK 版本
| SDK | 版本 | 用途 |
|-----|------|------|
| nv-codec-headers | n12.1.14.0 | NVIDIA 编码头文件 |
| Video_Codec_SDK | 12.1.14 | NVIDIA 编解码 SDK |
| AMF | v1.4.35 | AMD Advanced Media Framework |
| MediaSDK | 22.5.4 | Intel Media SDK |
### 7.2 FFmpeg 依赖库
```
libavcodec - 编解码核心
libavutil - 工具函数
libavformat - 容器格式
libswscale - 图像缩放转换
```
## 8. 总结
hwcodec 库通过 Rust/C++ 混合架构,在保证内存安全的同时实现了高性能的视频编解码。其核心设计特点包括:
1. **统一的编解码器 API**: 无论使用硬件还是软件编解码,上层 API 保持一致
2. **自动硬件检测**: 运行时自动检测并选择最优的硬件加速后端
3. **优先级系统**: 基于质量和性能为不同编码器分配优先级
4. **低延迟优化**: 针对实时流媒体场景进行了专门优化
5. **跨平台支持**: 覆盖主流操作系统和 GPU 厂商

View File

@@ -0,0 +1,445 @@
# hwcodec 编解码器 API 详解
## 1. 编码器 API
### 1.1 编码器初始化
#### EncodeContext 参数
```rust
pub struct EncodeContext {
pub name: String, // 编码器名称
pub mc_name: Option<String>, // MediaCodec 名称 (Android)
pub width: i32, // 视频宽度 (必须为偶数)
pub height: i32, // 视频高度 (必须为偶数)
pub pixfmt: AVPixelFormat, // 像素格式
pub align: i32, // 内存对齐 (通常为 0 或 32)
pub fps: i32, // 帧率
pub gop: i32, // GOP 大小 (关键帧间隔)
pub rc: RateControl, // 码率控制模式
pub quality: Quality, // 编码质量
pub kbs: i32, // 目标码率 (kbps)
pub q: i32, // 量化参数 (CQ 模式)
pub thread_count: i32, // 编码线程数
}
```
#### 参数说明
| 参数 | 类型 | 说明 | 推荐值 |
|------|------|------|--------|
| `name` | String | FFmpeg 编码器名称 | 见下表 |
| `width` | i32 | 视频宽度 | 1920 |
| `height` | i32 | 视频高度 | 1080 |
| `pixfmt` | AVPixelFormat | 像素格式 | NV12 / YUV420P |
| `align` | i32 | 内存对齐 | 0 (自动) |
| `fps` | i32 | 帧率 | 30 |
| `gop` | i32 | GOP 大小 | 30 (1秒) |
| `rc` | RateControl | 码率控制 | CBR / VBR |
| `quality` | Quality | 质量级别 | Medium |
| `kbs` | i32 | 码率 (kbps) | 2000-8000 |
| `thread_count` | i32 | 线程数 | 4 |
#### 编码器名称对照表
| 名称 | 格式 | 加速 | 平台 |
|------|------|------|------|
| `h264_nvenc` | H.264 | NVIDIA GPU | Windows/Linux |
| `hevc_nvenc` | H.265 | NVIDIA GPU | Windows/Linux |
| `h264_amf` | H.264 | AMD GPU | Windows/Linux |
| `hevc_amf` | H.265 | AMD GPU | Windows/Linux |
| `h264_qsv` | H.264 | Intel QSV | Windows |
| `hevc_qsv` | H.265 | Intel QSV | Windows |
| `h264_vaapi` | H.264 | VAAPI | Linux |
| `hevc_vaapi` | H.265 | VAAPI | Linux |
| `vp8_vaapi` | VP8 | VAAPI | Linux |
| `vp9_vaapi` | VP9 | VAAPI | Linux |
| `h264_rkmpp` | H.264 | Rockchip MPP | Linux |
| `hevc_rkmpp` | H.265 | Rockchip MPP | Linux |
| `h264_v4l2m2m` | H.264 | V4L2 M2M | Linux |
| `hevc_v4l2m2m` | H.265 | V4L2 M2M | Linux |
| `hevc_videotoolbox` | H.265 | VideoToolbox | macOS |
| `h264` | H.264 | 软件 (x264) | 全平台 |
| `hevc` | H.265 | 软件 (x265) | 全平台 |
| `libvpx` | VP8 | 软件 | 全平台 |
| `libvpx-vp9` | VP9 | 软件 | 全平台 |
| `mjpeg` | MJPEG | 软件 | 全平台 |
### 1.2 创建编码器
```rust
use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext};
use hwcodec::ffmpeg::{AVPixelFormat};
use hwcodec::common::{RateControl, Quality};
let ctx = EncodeContext {
name: "h264_vaapi".to_string(),
mc_name: None,
width: 1920,
height: 1080,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
align: 0,
fps: 30,
gop: 30,
rc: RateControl::RC_CBR,
quality: Quality::Quality_Medium,
kbs: 4000,
q: 0,
thread_count: 4,
};
let encoder = Encoder::new(ctx)?;
println!("Linesize: {:?}", encoder.linesize);
println!("Offset: {:?}", encoder.offset);
println!("Buffer length: {}", encoder.length);
```
### 1.3 编码帧
```rust
// 准备 YUV 数据
let yuv_data: Vec<u8> = prepare_yuv_frame();
// 编码
let pts_ms: i64 = 0; // 时间戳 (毫秒)
match encoder.encode(&yuv_data, pts_ms) {
Ok(frames) => {
for frame in frames.iter() {
println!("Encoded: {} bytes, pts={}, key={}",
frame.data.len(), frame.pts, frame.key);
// 发送 frame.data
}
}
Err(code) => {
eprintln!("Encode error: {}", code);
}
}
```
### 1.4 动态调整码率
```rust
// 动态调整到 6000 kbps
encoder.set_bitrate(6000)?;
```
### 1.5 请求关键帧
```rust
// 下一帧强制编码为 IDR 帧
encoder.request_keyframe();
```
### 1.6 检测可用编码器
```rust
use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext};
use hwcodec::ffmpeg_ram::CodecInfo;
let ctx = EncodeContext {
name: String::new(),
mc_name: None,
width: 1920,
height: 1080,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
align: 0,
fps: 30,
gop: 30,
rc: RateControl::RC_DEFAULT,
quality: Quality::Quality_Default,
kbs: 4000,
q: 0,
thread_count: 4,
};
let available_encoders = Encoder::available_encoders(ctx, None);
for encoder in available_encoders {
println!("Available: {} (format: {:?}, priority: {})",
encoder.name, encoder.format, encoder.priority);
}
```
## 2. 解码器 API
### 2.1 解码器初始化
#### DecodeContext 参数
```rust
pub struct DecodeContext {
pub name: String, // 解码器名称
pub device_type: AVHWDeviceType, // 硬件设备类型
pub thread_count: i32, // 解码线程数
}
```
#### 硬件设备类型
| AVHWDeviceType | 说明 |
|----------------|------|
| `AV_HWDEVICE_TYPE_NONE` | 软件解码 |
| `AV_HWDEVICE_TYPE_CUDA` | NVIDIA CUDA |
| `AV_HWDEVICE_TYPE_VAAPI` | Linux VAAPI |
| `AV_HWDEVICE_TYPE_D3D11VA` | Windows D3D11 |
| `AV_HWDEVICE_TYPE_VIDEOTOOLBOX` | macOS VideoToolbox |
| `AV_HWDEVICE_TYPE_MEDIACODEC` | Android MediaCodec |
### 2.2 创建解码器
```rust
use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext};
use hwcodec::ffmpeg::AVHWDeviceType;
let ctx = DecodeContext {
name: "h264".to_string(),
device_type: AVHWDeviceType::AV_HWDEVICE_TYPE_VAAPI,
thread_count: 4,
};
let decoder = Decoder::new(ctx)?;
```
### 2.3 解码帧
```rust
// 输入编码数据
let encoded_packet: Vec<u8> = receive_encoded_data();
match decoder.decode(&encoded_packet) {
Ok(frames) => {
for frame in frames.iter() {
println!("Decoded: {}x{}, format={:?}, key={}",
frame.width, frame.height, frame.pixfmt, frame.key);
// 访问 YUV 数据
let y_plane = &frame.data[0];
let u_plane = &frame.data[1];
let v_plane = &frame.data[2]; // 仅 YUV420P
}
}
Err(code) => {
eprintln!("Decode error: {}", code);
}
}
```
### 2.4 DecodeFrame 结构体
```rust
pub struct DecodeFrame {
pub pixfmt: AVPixelFormat, // 输出像素格式
pub width: i32, // 帧宽度
pub height: i32, // 帧高度
pub data: Vec<Vec<u8>>, // 平面数据 [Y, U, V] 或 [Y, UV]
pub linesize: Vec<i32>, // 每个平面的行字节数
pub key: bool, // 是否为关键帧
}
```
#### 像素格式与平面布局
| 像素格式 | 平面数 | data[0] | data[1] | data[2] |
|----------|--------|---------|---------|---------|
| `YUV420P` | 3 | Y | U | V |
| `YUVJ420P` | 3 | Y | U | V |
| `YUV422P` | 3 | Y | U | V |
| `NV12` | 2 | Y | UV (交错) | - |
| `NV21` | 2 | Y | VU (交错) | - |
### 2.5 检测可用解码器
```rust
use hwcodec::ffmpeg_ram::decode::Decoder;
let available_decoders = Decoder::available_decoders();
for decoder in available_decoders {
println!("Available: {} (format: {:?}, hwdevice: {:?})",
decoder.name, decoder.format, decoder.hwdevice);
}
```
## 3. 码率控制模式
### 3.1 RateControl 枚举
```rust
pub enum RateControl {
RC_DEFAULT, // 使用编码器默认
RC_CBR, // 恒定码率
RC_VBR, // 可变码率
RC_CQ, // 恒定质量 (需设置 q 参数)
}
```
### 3.2 模式说明
| 模式 | 说明 | 适用场景 |
|------|------|----------|
| `RC_CBR` | 码率恒定,质量随场景变化 | 网络带宽受限 |
| `RC_VBR` | 质量优先,码率波动 | 本地存储 |
| `RC_CQ` | 恒定质量,码率波动大 | 质量敏感场景 |
### 3.3 各编码器支持情况
| 编码器 | CBR | VBR | CQ |
|--------|-----|-----|-----|
| nvenc | ✓ | ✓ | ✓ |
| amf | ✓ | ✓ (低延迟) | ✗ |
| qsv | ✓ | ✓ | ✗ |
| vaapi | ✓ | ✓ | ✗ |
| mediacodec | ✓ | ✓ | ✓ |
## 4. 质量等级
### 4.1 Quality 枚举
```rust
pub enum Quality {
Quality_Default, // 使用编码器默认
Quality_High, // 高质量 (慢速)
Quality_Medium, // 中等质量 (平衡)
Quality_Low, // 低质量 (快速)
}
```
### 4.2 编码器预设映射
| 质量 | nvenc | amf | qsv |
|------|-------|-----|-----|
| High | - | quality | veryslow |
| Medium | p4 | balanced | medium |
| Low | p1 | speed | veryfast |
## 5. 混流器 API
### 5.1 创建混流器
```rust
use hwcodec::mux::{Muxer, MuxContext};
let ctx = MuxContext {
filename: "/tmp/output.mp4".to_string(),
width: 1920,
height: 1080,
is265: false, // H.264
framerate: 30,
};
let muxer = Muxer::new(ctx)?;
```
### 5.2 写入视频帧
```rust
// 编码后的帧数据
let encoded_data: Vec<u8> = encoder.encode(...)?;
let is_keyframe = true;
muxer.write_video(&encoded_data, is_keyframe)?;
```
### 5.3 完成写入
```rust
// 写入文件尾
muxer.write_tail()?;
// muxer 被 drop 时自动释放资源
```
## 6. 错误处理
### 6.1 错误码
| 错误码 | 常量 | 说明 |
|--------|------|------|
| 0 | `HWCODEC_SUCCESS` | 成功 |
| -1 | `HWCODEC_ERR_COMMON` | 通用错误 |
| -2 | `HWCODEC_ERR_HEVC_COULD_NOT_FIND_POC` | HEVC 解码参考帧丢失 |
### 6.2 常见错误处理
```rust
match encoder.encode(&yuv_data, pts) {
Ok(frames) => {
// 处理编码帧
}
Err(-1) => {
eprintln!("编码失败,可能是输入数据格式错误");
}
Err(code) => {
eprintln!("未知错误: {}", code);
}
}
```
## 7. 最佳实践
### 7.1 编码器选择策略
```rust
fn select_best_encoder(
width: i32,
height: i32,
format: DataFormat
) -> Option<String> {
let ctx = EncodeContext {
width,
height,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
// ... 其他参数
};
let encoders = Encoder::available_encoders(ctx, None);
// 按优先级排序,选择最佳
encoders.into_iter()
.filter(|e| e.format == format)
.min_by_key(|e| e.priority)
.map(|e| e.name)
}
```
### 7.2 帧内存布局
```rust
// 获取 NV12 帧布局信息
let (linesize, offset, length) = ffmpeg_linesize_offset_length(
AVPixelFormat::AV_PIX_FMT_NV12,
1920,
1080,
0, // align
)?;
// 分配缓冲区
let mut buffer = vec![0u8; length as usize];
// 填充 Y 平面: buffer[0..offset[0]]
// 填充 UV 平面: buffer[offset[0]..length]
```
### 7.3 关键帧控制
```rust
let mut frame_count = 0;
loop {
// 每 30 帧强制一个关键帧
if frame_count % 30 == 0 {
encoder.request_keyframe();
}
encoder.encode(&yuv_data, pts)?;
frame_count += 1;
}
```
### 7.4 线程安全
```rust
// Decoder 实现了 Send + Sync
unsafe impl Send for Decoder {}
unsafe impl Sync for Decoder {}
// 可以安全地在多线程间传递
let decoder = Arc::new(Mutex::new(Decoder::new(ctx)?));
```

View File

@@ -0,0 +1,615 @@
# hwcodec 硬件加速详解
## 1. 硬件加速架构
### 1.1 整体流程
```
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (Rust) │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Encoder::available_encoders() → 自动检测可用硬件编码器 ││
│ └─────────────────────────────────────────────────────────┘│
└────────────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 硬件检测层 (C++) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐│
│ │linux_ │ │linux_ │ │linux_ │ │linux_support_ ││
│ │support_nv│ │support_ │ │support_ │ │rkmpp/v4l2m2m ││
│ └────┬─────┘ │amd │ │intel │ └─────────┬────────┘│
│ │ └────┬─────┘ └────┬─────┘ │ │
└───────┼────────────┼────────────┼─────────────────┼─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────────────┐
│ CUDA/ │ │ AMF │ │ VPL/MFX │ │ 设备节点检测 │
│ NVENC │ │ Runtime │ │ Library │ │ /dev/mpp_service │
│ 动态库 │ │ 动态库 │ │ 动态库 │ │ /dev/video* │
└───────────┘ └───────────┘ └───────────┘ └───────────────────┘
```
### 1.2 编码器测试验证
每个检测到的硬件编码器都会进行实际编码测试:
```rust
// libs/hwcodec/src/ffmpeg_ram/encode.rs:358-450
// 生成测试用 YUV 数据
let yuv = Encoder::dummy_yuv(ctx.clone())?;
// 尝试创建编码器并编码测试帧
match Encoder::new(c) {
Ok(mut encoder) => {
let start = std::time::Instant::now();
match encoder.encode(&yuv, 0) {
Ok(frames) => {
let elapsed = start.elapsed().as_millis();
// 验证: 必须产生 1 帧且为关键帧,且在 1 秒内完成
if frames.len() == 1 && frames[0].key == 1
&& elapsed < TEST_TIMEOUT_MS {
res.push(codec);
}
}
Err(_) => { /* 编码失败,跳过 */ }
}
}
Err(_) => { /* 创建失败,跳过 */ }
}
```
## 2. NVIDIA NVENC/NVDEC
### 2.1 检测机制 (Linux)
```cpp
// libs/hwcodec/cpp/common/platform/linux/linux.cpp:57-73
int linux_support_nv() {
CudaFunctions *cuda_dl = NULL;
NvencFunctions *nvenc_dl = NULL;
CuvidFunctions *cvdl = NULL;
// 加载 CUDA 动态库
if (cuda_load_functions(&cuda_dl, NULL) < 0)
throw "cuda_load_functions failed";
// 加载 NVENC 动态库
if (nvenc_load_functions(&nvenc_dl, NULL) < 0)
throw "nvenc_load_functions failed";
// 加载 CUVID (解码) 动态库
if (cuvid_load_functions(&cvdl, NULL) < 0)
throw "cuvid_load_functions failed";
// 全部成功则支持 NVIDIA 硬件加速
return 0;
}
```
### 2.2 编码配置
```cpp
// libs/hwcodec/cpp/common/util.cpp
// NVENC 低延迟配置
if (name.find("nvenc") != std::string::npos) {
// 禁用编码延迟
av_opt_set(priv_data, "delay", "0", 0);
}
// GPU 选择
if (name.find("nvenc") != std::string::npos) {
av_opt_set_int(priv_data, "gpu", gpu_index, 0);
}
// 质量预设
switch (quality) {
case Quality_Medium:
av_opt_set(priv_data, "preset", "p4", 0);
break;
case Quality_Low:
av_opt_set(priv_data, "preset", "p1", 0);
break;
}
// 码率控制
av_opt_set(priv_data, "rc", "cbr", 0); // 或 "vbr"
```
### 2.3 环境变量
| 变量 | 说明 |
|------|------|
| `RUSTDESK_HWCODEC_NVENC_GPU` | 指定使用的 GPU 索引 (-1 = 自动) |
### 2.4 依赖库
- `libcuda.so` - CUDA 运行时
- `libnvidia-encode.so` - NVENC 编码器
- `libnvcuvid.so` - NVDEC 解码器
## 3. AMD AMF
### 3.1 检测机制 (Linux)
```cpp
// libs/hwcodec/cpp/common/platform/linux/linux.cpp:75-91
int linux_support_amd() {
#if defined(__x86_64__) || defined(__aarch64__)
#define AMF_DLL_NAMEA "libamfrt64.so.1"
#else
#define AMF_DLL_NAMEA "libamfrt32.so.1"
#endif
void *handle = dlopen(AMF_DLL_NAMEA, RTLD_LAZY);
if (!handle) {
return -1; // AMF 不可用
}
dlclose(handle);
return 0; // AMF 可用
}
```
### 3.2 编码配置
```cpp
// libs/hwcodec/cpp/common/util.cpp
// AMF 低延迟配置
if (name.find("amf") != std::string::npos) {
av_opt_set(priv_data, "query_timeout", "1000", 0);
}
// 质量预设
switch (quality) {
case Quality_High:
av_opt_set(priv_data, "quality", "quality", 0);
break;
case Quality_Medium:
av_opt_set(priv_data, "quality", "balanced", 0);
break;
case Quality_Low:
av_opt_set(priv_data, "quality", "speed", 0);
break;
}
// 码率控制
av_opt_set(priv_data, "rc", "cbr", 0); // 恒定码率
av_opt_set(priv_data, "rc", "vbr_latency", 0); // 低延迟 VBR
```
### 3.3 依赖库
- `libamfrt64.so.1` (64位) 或 `libamfrt32.so.1` (32位)
### 3.4 外部 SDK
```
externals/AMF_v1.4.35/
├── amf/
│ ├── public/common/ # 公共代码
│ │ ├── AMFFactory.cpp
│ │ ├── Thread.cpp
│ │ └── TraceAdapter.cpp
│ └── public/include/ # 头文件
│ ├── components/ # 组件定义
│ └── core/ # 核心定义
```
## 4. Intel QSV/MFX
### 4.1 检测机制 (Linux)
```cpp
// libs/hwcodec/cpp/common/platform/linux/linux.cpp:93-107
int linux_support_intel() {
const char *libs[] = {
"libvpl.so", // oneVPL (新版)
"libmfx.so", // Media SDK
"libmfx-gen.so.1.2", // 新驱动
"libmfxhw64.so.1" // 旧版驱动
};
for (size_t i = 0; i < sizeof(libs) / sizeof(libs[0]); i++) {
void *handle = dlopen(libs[i], RTLD_LAZY);
if (handle) {
dlclose(handle);
return 0; // 找到可用库
}
}
return -1; // Intel MFX 不可用
}
```
### 4.2 编码配置
```cpp
// libs/hwcodec/cpp/common/util.cpp
// QSV 低延迟配置
if (name.find("qsv") != std::string::npos) {
av_opt_set(priv_data, "async_depth", "1", 0);
}
// QSV 特殊码率配置
if (name.find("qsv") != std::string::npos) {
c->rc_max_rate = c->bit_rate;
c->bit_rate--; // 实现 CBR 效果
}
// 质量预设
switch (quality) {
case Quality_High:
av_opt_set(priv_data, "preset", "veryslow", 0);
break;
case Quality_Medium:
av_opt_set(priv_data, "preset", "medium", 0);
break;
case Quality_Low:
av_opt_set(priv_data, "preset", "veryfast", 0);
break;
}
// 严格标准兼容性 (用于某些特殊设置)
c->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL;
```
### 4.3 限制
- QSV 不支持 `YUV420P` 像素格式,必须使用 `NV12`
- 仅在 Windows 平台完全支持
### 4.4 外部 SDK
```
externals/MediaSDK_22.5.4/
├── api/
│ ├── include/ # MFX 头文件
│ ├── mfx_dispatch/ # MFX 调度器
│ └── mediasdk_structures/ # 数据结构
└── samples/sample_common/ # 示例代码
```
## 5. VAAPI (Linux)
### 5.1 工作原理
VAAPI (Video Acceleration API) 是 Linux 上的通用硬件视频加速接口:
```
┌─────────────────────────────────────────────────────────────┐
│ Application │
├─────────────────────────────────────────────────────────────┤
│ FFmpeg libavcodec │
├─────────────────────────────────────────────────────────────┤
│ VAAPI (libva) │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ Intel i965 │ Intel iHD │ AMD radeonsi │ NVIDIA VDPAU │
│ (Gen8-) │ (Gen9+) │ │ (via wrapper) │
├──────────────┴──────────────┴──────────────┴────────────────┤
│ Kernel DRM Driver │
├──────────────┬──────────────┬──────────────┬────────────────┤
│ i915 │ amdgpu │ nvidia │ ... │
└──────────────┴──────────────┴──────────────┴────────────────┘
```
### 5.2 编码配置
```cpp
// libs/hwcodec/cpp/common/util.cpp
// VAAPI 低延迟配置
if (name.find("vaapi") != std::string::npos) {
av_opt_set(priv_data, "async_depth", "1", 0);
}
```
### 5.3 硬件上下文初始化
```cpp
// libs/hwcodec/cpp/ffmpeg_ram/ffmpeg_ram_encode.cpp
// 检测 VAAPI 编码器
if (name_.find("vaapi") != std::string::npos) {
hw_device_type_ = AV_HWDEVICE_TYPE_VAAPI;
hw_pixfmt_ = AV_PIX_FMT_VAAPI;
}
// 创建硬件设备上下文
ret = av_hwdevice_ctx_create(&hw_device_ctx_, hw_device_type_,
NULL, // 使用默认设备
NULL, 0);
// 设置硬件帧上下文
set_hwframe_ctx();
// 分配硬件帧
hw_frame_ = av_frame_alloc();
av_hwframe_get_buffer(c_->hw_frames_ctx, hw_frame_, 0);
```
### 5.4 编码流程
```
输入 YUV (CPU 内存)
av_hwframe_transfer_data(hw_frame_, frame_, 0) // CPU → GPU
avcodec_send_frame(c_, hw_frame_) // 发送 GPU 帧
avcodec_receive_packet(c_, pkt_) // 获取编码数据
编码数据 (CPU 内存)
```
### 5.5 依赖库
- `libva.so` - VAAPI 核心库
- `libva-drm.so` - DRM 后端
- `libva-x11.so` - X11 后端 (可选)
## 6. Rockchip MPP
### 6.1 检测机制
```cpp
// libs/hwcodec/cpp/common/platform/linux/linux.cpp:122-137
int linux_support_rkmpp() {
// 检测 MPP 服务设备
if (access("/dev/mpp_service", F_OK) == 0) {
return 0; // MPP 可用
}
// 备用: 检测 RGA 设备
if (access("/dev/rga", F_OK) == 0) {
return 0; // MPP 可能可用
}
return -1; // MPP 不可用
}
```
### 6.2 支持的编码器
| 编码器 | 优先级 | 说明 |
|--------|--------|------|
| `h264_rkmpp` | Best (0) | H.264 硬件编码 |
| `hevc_rkmpp` | Best (0) | H.265 硬件编码 |
### 6.3 适用设备
- Rockchip RK3328 (Onecloud, Chainedbox)
- Rockchip RK3399/RK3588 系列
- 其他 Rockchip SoC
## 7. V4L2 M2M
### 7.1 检测机制
```cpp
// libs/hwcodec/cpp/common/platform/linux/linux.cpp:139-163
int linux_support_v4l2m2m() {
const char *m2m_devices[] = {
"/dev/video10", // 常见 M2M 编码设备
"/dev/video11", // 常见 M2M 解码设备
"/dev/video0", // 某些 SoC 使用
};
for (size_t i = 0; i < sizeof(m2m_devices) / sizeof(m2m_devices[0]); i++) {
if (access(m2m_devices[i], F_OK) == 0) {
int fd = open(m2m_devices[i], O_RDWR | O_NONBLOCK);
if (fd >= 0) {
close(fd);
return 0; // V4L2 M2M 可用
}
}
}
return -1;
}
```
### 7.2 支持的编码器
| 编码器 | 优先级 | 说明 |
|--------|--------|------|
| `h264_v4l2m2m` | Good (1) | H.264 V4L2 编码 |
| `hevc_v4l2m2m` | Good (1) | H.265 V4L2 编码 |
### 7.3 适用设备
- 通用 ARM SoC (Allwinner, Amlogic 等)
- 支持 V4L2 M2M API 的设备
## 8. Apple VideoToolbox
### 8.1 检测机制 (macOS)
```rust
// libs/hwcodec/src/common.rs:57-87
#[cfg(target_os = "macos")]
pub(crate) fn get_video_toolbox_codec_support() -> (bool, bool, bool, bool) {
extern "C" {
fn checkVideoToolboxSupport(
h264_encode: *mut i32,
h265_encode: *mut i32,
h264_decode: *mut i32,
h265_decode: *mut i32,
) -> c_void;
}
let mut h264_encode = 0;
let mut h265_encode = 0;
let mut h264_decode = 0;
let mut h265_decode = 0;
unsafe {
checkVideoToolboxSupport(&mut h264_encode, &mut h265_encode,
&mut h264_decode, &mut h265_decode);
}
(h264_encode == 1, h265_encode == 1,
h264_decode == 1, h265_decode == 1)
}
```
### 8.2 编码配置
```cpp
// libs/hwcodec/cpp/common/util.cpp
// VideoToolbox 低延迟配置
if (name.find("videotoolbox") != std::string::npos) {
av_opt_set_int(priv_data, "realtime", 1, 0);
av_opt_set_int(priv_data, "prio_speed", 1, 0);
}
// 强制硬件编码
if (name.find("videotoolbox") != std::string::npos) {
av_opt_set_int(priv_data, "allow_sw", 0, 0);
}
```
### 8.3 限制
- H.264 编码不稳定,已禁用
- 仅支持 H.265 编码
- 完全支持 H.264/H.265 解码
### 8.4 依赖框架
```
CoreFoundation
CoreVideo
CoreMedia
VideoToolbox
AVFoundation
```
## 9. 硬件加速优先级
### 9.1 优先级定义
```rust
pub enum Priority {
Best = 0, // 专用硬件编码器
Good = 1, // 通用硬件加速
Normal = 2, // 基本硬件支持
Soft = 3, // 软件编码
Bad = 4, // 最低优先级
}
```
### 9.2 各编码器优先级
| 优先级 | 编码器 |
|--------|--------|
| Best (0) | nvenc, amf, qsv, rkmpp |
| Good (1) | vaapi, v4l2m2m |
| Soft (3) | x264, x265, libvpx |
### 9.3 选择策略
```rust
// libs/hwcodec/src/ffmpeg_ram/mod.rs:49-117
pub fn prioritized(coders: Vec<CodecInfo>) -> CodecInfos {
// 对于每种格式,选择优先级最高的编码器
for coder in coders {
match coder.format {
DataFormat::H264 => {
if h264.is_none() || h264.priority > coder.priority {
h264 = Some(coder);
}
}
// ... 其他格式类似
}
}
}
```
## 10. 故障排除
### 10.1 NVIDIA
```bash
# 检查 NVIDIA 驱动
nvidia-smi
# 检查 NVENC 支持
ls /dev/nvidia*
# 检查 CUDA 库
ldconfig -p | grep cuda
ldconfig -p | grep nvidia-encode
```
### 10.2 AMD
```bash
# 检查 AMD 驱动
lspci | grep AMD
# 检查 AMF 库
ldconfig -p | grep amf
```
### 10.3 Intel
```bash
# 检查 Intel 驱动
vainfo
# 检查 MFX 库
ldconfig -p | grep mfx
ldconfig -p | grep vpl
```
### 10.4 VAAPI
```bash
# 安装 vainfo
sudo apt install vainfo
# 检查 VAAPI 支持
vainfo
# 输出示例:
# libva info: VA-API version 1.14.0
# libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so
# vainfo: Driver version: Intel iHD driver for Intel(R) Gen Graphics
# vainfo: Supported profile and entrypoints
# VAProfileH264Main : VAEntrypointVLD
# VAProfileH264Main : VAEntrypointEncSlice
# ...
```
### 10.5 Rockchip MPP
```bash
# 检查 MPP 设备
ls -la /dev/mpp_service
ls -la /dev/rga
# 检查 MPP 库
ldconfig -p | grep rockchip_mpp
```
### 10.6 V4L2 M2M
```bash
# 列出 V4L2 设备
v4l2-ctl --list-devices
# 检查设备能力
v4l2-ctl -d /dev/video10 --all
```

View File

@@ -0,0 +1,539 @@
# hwcodec 构建系统与集成指南
## 1. 项目结构
```
libs/hwcodec/
├── Cargo.toml # 包配置
├── Cargo.lock # 依赖锁定
├── build.rs # 构建脚本
├── src/ # Rust 源码
│ ├── lib.rs # 库入口
│ ├── common.rs # 公共定义
│ ├── ffmpeg.rs # FFmpeg 集成
│ ├── mux.rs # 混流器
│ ├── android.rs # Android 支持
│ ├── ffmpeg_ram/ # RAM 编解码
│ │ ├── mod.rs
│ │ ├── encode.rs
│ │ └── decode.rs
│ ├── vram/ # GPU 编解码 (Windows)
│ │ ├── mod.rs
│ │ ├── encode.rs
│ │ ├── decode.rs
│ │ └── ...
│ └── res/ # 测试资源
│ ├── 720p.h264
│ └── 720p.h265
├── cpp/ # C++ 源码
│ ├── common/ # 公共代码
│ ├── ffmpeg_ram/ # FFmpeg RAM 实现
│ ├── ffmpeg_vram/ # FFmpeg VRAM 实现
│ ├── nv/ # NVIDIA 实现
│ ├── amf/ # AMD 实现
│ ├── mfx/ # Intel 实现
│ ├── mux/ # 混流实现
│ └── yuv/ # YUV 处理
├── externals/ # 外部 SDK (Git 子模块)
│ ├── nv-codec-headers_n12.1.14.0/
│ ├── Video_Codec_SDK_12.1.14/
│ ├── AMF_v1.4.35/
│ └── MediaSDK_22.5.4/
├── dev/ # 开发工具
│ ├── capture/ # 捕获工具
│ ├── render/ # 渲染工具
│ └── tool/ # 通用工具
└── examples/ # 示例程序
```
## 2. Cargo 配置
### 2.1 Cargo.toml
```toml
[package]
name = "hwcodec"
version = "0.7.1"
edition = "2021"
[features]
default = []
vram = [] # GPU VRAM 直接编解码 (仅 Windows)
[dependencies]
log = "0.4" # 日志
serde_derive = "1.0" # 序列化派生宏
serde = "1.0" # 序列化
serde_json = "1.0" # JSON 序列化
[build-dependencies]
cc = "1.0" # C++ 编译
bindgen = "0.59" # FFI 绑定生成
[dev-dependencies]
env_logger = "0.10" # 日志输出
rand = "0.8" # 随机数
```
### 2.2 Feature 说明
| Feature | 说明 | 平台 |
|---------|------|------|
| `default` | 基础功能 | 全平台 |
| `vram` | GPU VRAM 直接编解码 | 仅 Windows |
### 2.3 使用方式
```toml
# 基础使用
[dependencies]
hwcodec = { path = "libs/hwcodec" }
# 启用 VRAM 功能 (Windows)
[dependencies]
hwcodec = { path = "libs/hwcodec", features = ["vram"] }
```
## 3. 构建脚本详解 (build.rs)
### 3.1 主入口
```rust
fn main() {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut builder = Build::new();
// 1. 构建公共模块
build_common(&mut builder);
// 2. 构建 FFmpeg 相关模块
ffmpeg::build_ffmpeg(&mut builder);
// 3. 构建 SDK 模块 (Windows + vram feature)
#[cfg(all(windows, feature = "vram"))]
sdk::build_sdk(&mut builder);
// 4. 编译生成静态库
builder.static_crt(true).compile("hwcodec");
}
```
### 3.2 公共模块构建
```rust
fn build_common(builder: &mut Build) {
let common_dir = manifest_dir.join("cpp").join("common");
// 生成 FFI 绑定
bindgen::builder()
.header(common_dir.join("common.h"))
.header(common_dir.join("callback.h"))
.rustified_enum("*")
.generate()
.write_to_file(OUT_DIR.join("common_ffi.rs"));
// 平台相关代码
#[cfg(windows)]
builder.file(common_dir.join("platform/win/win.cpp"));
#[cfg(target_os = "linux")]
builder.file(common_dir.join("platform/linux/linux.cpp"));
#[cfg(target_os = "macos")]
builder.file(common_dir.join("platform/mac/mac.mm"));
// 工具代码
builder.files([
common_dir.join("log.cpp"),
common_dir.join("util.cpp"),
]);
}
```
### 3.3 FFmpeg 模块构建
```rust
mod ffmpeg {
pub fn build_ffmpeg(builder: &mut Build) {
// 生成 FFmpeg FFI 绑定
ffmpeg_ffi();
// 链接 FFmpeg 库
if let Ok(vcpkg_root) = std::env::var("VCPKG_ROOT") {
link_vcpkg(builder, vcpkg_root.into());
} else {
link_system_ffmpeg(builder); // pkg-config
}
// 链接系统库
link_os();
// 构建子模块
build_ffmpeg_ram(builder);
#[cfg(feature = "vram")]
build_ffmpeg_vram(builder);
build_mux(builder);
}
}
```
### 3.4 FFmpeg 链接方式
#### VCPKG (跨平台静态链接)
```rust
fn link_vcpkg(builder: &mut Build, path: PathBuf) -> PathBuf {
// 目标平台识别
let target = match (target_os, target_arch) {
("windows", "x86_64") => "x64-windows-static",
("macos", "x86_64") => "x64-osx",
("macos", "aarch64") => "arm64-osx",
("linux", arch) => format!("{}-linux", arch),
_ => panic!("unsupported platform"),
};
let lib_path = path.join("installed").join(target).join("lib");
// 链接 FFmpeg 静态库
println!("cargo:rustc-link-search=native={}", lib_path);
["avcodec", "avutil", "avformat"].iter()
.for_each(|lib| println!("cargo:rustc-link-lib=static={}", lib));
}
```
#### pkg-config (Linux 动态链接)
```rust
fn link_system_ffmpeg(builder: &mut Build) {
let libs = ["libavcodec", "libavutil", "libavformat", "libswscale"];
for lib in &libs {
// 获取编译标志
let cflags = Command::new("pkg-config")
.args(["--cflags", lib])
.output()?;
// 获取链接标志
let libs = Command::new("pkg-config")
.args(["--libs", lib])
.output()?;
// 解析并应用
for flag in libs.split_whitespace() {
if flag.starts_with("-L") {
println!("cargo:rustc-link-search=native={}", &flag[2..]);
} else if flag.starts_with("-l") {
println!("cargo:rustc-link-lib={}", &flag[2..]);
}
}
}
}
```
### 3.5 系统库链接
```rust
fn link_os() {
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let libs: Vec<&str> = match target_os.as_str() {
"windows" => vec!["User32", "bcrypt", "ole32", "advapi32"],
"linux" => vec!["drm", "X11", "stdc++", "z"],
"macos" | "ios" => vec!["c++", "m"],
"android" => vec!["z", "m", "android", "atomic", "mediandk"],
_ => panic!("unsupported os"),
};
for lib in libs {
println!("cargo:rustc-link-lib={}", lib);
}
// macOS 框架
if target_os == "macos" || target_os == "ios" {
for framework in ["CoreFoundation", "CoreVideo", "CoreMedia",
"VideoToolbox", "AVFoundation"] {
println!("cargo:rustc-link-lib=framework={}", framework);
}
}
}
```
### 3.6 SDK 模块构建 (Windows)
```rust
#[cfg(all(windows, feature = "vram"))]
mod sdk {
pub fn build_sdk(builder: &mut Build) {
build_amf(builder); // AMD AMF
build_nv(builder); // NVIDIA
build_mfx(builder); // Intel MFX
}
fn build_nv(builder: &mut Build) {
let sdk_path = externals_dir.join("Video_Codec_SDK_12.1.14");
// 包含 SDK 头文件
builder.includes([
sdk_path.join("Interface"),
sdk_path.join("Samples/Utils"),
sdk_path.join("Samples/NvCodec"),
]);
// 编译 SDK 源文件
builder.file(sdk_path.join("Samples/NvCodec/NvEncoder/NvEncoder.cpp"));
builder.file(sdk_path.join("Samples/NvCodec/NvEncoder/NvEncoderD3D11.cpp"));
builder.file(sdk_path.join("Samples/NvCodec/NvDecoder/NvDecoder.cpp"));
// 编译封装代码
builder.files([
nv_dir.join("nv_encode.cpp"),
nv_dir.join("nv_decode.cpp"),
]);
}
}
```
## 4. FFI 绑定生成
### 4.1 bindgen 配置
```rust
bindgen::builder()
.header("path/to/header.h")
.rustified_enum("*") // 生成 Rust 枚举
.parse_callbacks(Box::new(Callbacks)) // 自定义回调
.generate()
.write_to_file(OUT_DIR.join("ffi.rs"));
```
### 4.2 自定义派生
```rust
#[derive(Debug)]
struct CommonCallbacks;
impl bindgen::callbacks::ParseCallbacks for CommonCallbacks {
fn add_derives(&self, name: &str) -> Vec<String> {
// 为特定类型添加序列化支持
match name {
"DataFormat" | "SurfaceFormat" | "API" => {
vec!["Serialize".to_string(), "Deserialize".to_string()]
}
_ => vec![],
}
}
}
```
### 4.3 生成的文件
| 文件 | 来源 | 内容 |
|------|------|------|
| `common_ffi.rs` | `common.h`, `callback.h` | 枚举、常量、回调类型 |
| `ffmpeg_ffi.rs` | `ffmpeg_ffi.h` | FFmpeg 日志级别、函数 |
| `ffmpeg_ram_ffi.rs` | `ffmpeg_ram_ffi.h` | 编解码器函数 |
| `mux_ffi.rs` | `mux_ffi.h` | 混流器函数 |
## 5. 外部依赖管理
### 5.1 Git 子模块
```bash
# 初始化子模块
git submodule update --init --recursive
# 更新子模块
git submodule update --remote externals
```
### 5.2 子模块配置 (.gitmodules)
```
[submodule "externals"]
path = libs/hwcodec/externals
url = https://github.com/rustdesk-org/externals.git
```
### 5.3 依赖版本
| 依赖 | 版本 | 用途 |
|------|------|------|
| nv-codec-headers | n12.1.14.0 | NVIDIA FFmpeg 编码头 |
| Video_Codec_SDK | 12.1.14 | NVIDIA 编解码 SDK |
| AMF | v1.4.35 | AMD Advanced Media Framework |
| MediaSDK | 22.5.4 | Intel Media SDK |
## 6. 平台构建指南
### 6.1 Linux 构建
```bash
# 安装 FFmpeg 开发库
sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
# 安装其他依赖
sudo apt install libdrm-dev libx11-dev pkg-config
# 构建
cargo build --release -p hwcodec
```
### 6.2 Windows 构建 (VCPKG)
```powershell
# 安装 VCPKG
git clone https://github.com/microsoft/vcpkg
cd vcpkg
./bootstrap-vcpkg.bat
# 安装 FFmpeg
./vcpkg install ffmpeg:x64-windows-static
# 设置环境变量
$env:VCPKG_ROOT = "C:\path\to\vcpkg"
# 构建
cargo build --release -p hwcodec --features vram
```
### 6.3 macOS 构建
```bash
# 安装 FFmpeg (Homebrew)
brew install ffmpeg pkg-config
# 或使用 VCPKG
export VCPKG_ROOT=/path/to/vcpkg
vcpkg install ffmpeg:arm64-osx # Apple Silicon
vcpkg install ffmpeg:x64-osx # Intel
# 构建
cargo build --release -p hwcodec
```
### 6.4 交叉编译
```bash
# 安装 cross
cargo install cross --git https://github.com/cross-rs/cross
# ARM64 Linux
cross build --release -p hwcodec --target aarch64-unknown-linux-gnu
# ARMv7 Linux
cross build --release -p hwcodec --target armv7-unknown-linux-gnueabihf
```
## 7. 集成到 One-KVM
### 7.1 依赖配置
```toml
# Cargo.toml
[dependencies]
hwcodec = { path = "libs/hwcodec" }
```
### 7.2 使用示例
```rust
use hwcodec::ffmpeg_ram::encode::{Encoder, EncodeContext};
use hwcodec::ffmpeg_ram::decode::{Decoder, DecodeContext};
use hwcodec::ffmpeg::AVPixelFormat;
// 检测可用编码器
let encoders = Encoder::available_encoders(ctx, None);
// 创建编码器
let encoder = Encoder::new(EncodeContext {
name: "h264_vaapi".to_string(),
width: 1920,
height: 1080,
pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
fps: 30,
gop: 30,
kbs: 4000,
// ...
})?;
// 编码
let frames = encoder.encode(&yuv_data, pts_ms)?;
```
### 7.3 日志集成
```rust
// hwcodec 使用 log crate与 One-KVM 日志系统兼容
use log::{debug, info, warn, error};
// C++ 层日志通过回调传递
#[no_mangle]
pub extern "C" fn hwcodec_log(level: i32, message: *const c_char) {
match level {
0 => error!("{}", message),
1 => warn!("{}", message),
2 => info!("{}", message),
3 => debug!("{}", message),
4 => trace!("{}", message),
_ => {}
}
}
```
## 8. 故障排除
### 8.1 编译错误
**FFmpeg 未找到**:
```
error: pkg-config failed for libavcodec
```
解决: 安装 FFmpeg 开发库
```bash
sudo apt install libavcodec-dev libavformat-dev libavutil-dev libswscale-dev
```
**bindgen 错误**:
```
error: failed to run custom build command for `hwcodec`
```
解决: 安装 clang
```bash
sudo apt install clang libclang-dev
```
### 8.2 链接错误
**符号未定义**:
```
undefined reference to `av_log_set_level'
```
解决: 检查 FFmpeg 库链接顺序,确保 pkg-config 正确配置
**动态库未找到**:
```
error while loading shared libraries: libavcodec.so.59
```
解决:
```bash
sudo ldconfig
# 或设置 LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
```
### 8.3 运行时错误
**硬件编码器不可用**:
```
Encoder h264_vaapi test failed
```
检查:
1. 驱动是否正确安装: `vainfo`
2. 权限是否足够: `ls -la /dev/dri/`
3. 用户是否在 video 组: `groups`
**解码失败**:
```
avcodec_receive_frame failed, ret = -11
```
解决: 这通常表示需要更多输入数据 (EAGAIN),是正常行为

View File

@@ -0,0 +1,69 @@
# RustDesk 通信协议技术报告
## 概述
本报告详细分析 RustDesk 远程桌面软件的客户端与服务器之间的通信协议,包括 Rendezvous 服务器hbbs、Relay 服务器hbbr以及客户端之间的 P2P 连接机制。
## 文档结构
| 文档 | 内容 |
|------|------|
| [01-architecture.md](01-architecture.md) | 整体架构设计 |
| [02-rendezvous-protocol.md](02-rendezvous-protocol.md) | Rendezvous 服务器协议 |
| [03-relay-protocol.md](03-relay-protocol.md) | Relay 服务器协议 |
| [04-p2p-connection.md](04-p2p-connection.md) | P2P 连接流程 |
| [05-message-format.md](05-message-format.md) | 消息格式定义 |
| [06-encryption.md](06-encryption.md) | 加密机制 |
| [07-nat-traversal.md](07-nat-traversal.md) | NAT 穿透技术 |
| [08-onekvm-comparison.md](08-onekvm-comparison.md) | **One-KVM 实现对比分析** |
## 核心组件
### 1. Rendezvous Server (hbbs)
- **功能**: ID 注册、Peer 发现、NAT 类型检测、连接协调
- **端口**: 21116 (TCP/UDP), 21115 (NAT 测试), 21118 (WebSocket)
- **源文件**: `rustdesk-server/src/rendezvous_server.rs`
### 2. Relay Server (hbbr)
- **功能**: 当 P2P 连接失败时提供数据中转
- **端口**: 21117 (TCP), 21119 (WebSocket)
- **源文件**: `rustdesk-server/src/relay_server.rs`
### 3. 客户端 (RustDesk)
- **功能**: 远程桌面控制、文件传输、屏幕共享
- **核心模块**:
- `rendezvous_mediator.rs` - 与 Rendezvous 服务器通信
- `client.rs` - 客户端连接逻辑
- `server/connection.rs` - 被控端连接处理
## 协议栈
```
┌─────────────────────────────────────────┐
│ Application Layer │
│ (Video/Audio/Keyboard/Mouse/File) │
├─────────────────────────────────────────┤
│ Message Layer │
│ (Protobuf Messages) │
├─────────────────────────────────────────┤
│ Security Layer │
│ (Sodium: X25519 + ChaCha20) │
├─────────────────────────────────────────┤
│ Transport Layer │
│ (TCP/UDP/WebSocket/KCP) │
└─────────────────────────────────────────┘
```
## 关键技术特点
1. **混合连接模式**: 优先尝试 P2P 直连,失败后自动切换到 Relay 中转
2. **多协议支持**: TCP、UDP、WebSocket、KCP
3. **端到端加密**: 使用 libsodium 实现的 X25519 密钥交换和 ChaCha20-Poly1305 对称加密
4. **NAT 穿透**: 支持 UDP 打洞和 TCP 打洞技术
5. **服务器签名**: 可选的服务器公钥签名验证,防止中间人攻击
## 版本信息
- 分析基于 RustDesk 最新版本源码
- Protocol Buffer 版本: proto3
- 加密库: libsodium (sodiumoxide)

View File

@@ -0,0 +1,218 @@
# RustDesk 架构设计
## 系统架构图
```
┌──────────────────────┐
│ Rendezvous Server │
│ (hbbs) │
│ Port: 21116 │
└──────────┬───────────┘
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Client A │ │ Client B │ │ Client C │
│ (控制端) │ │ (被控端) │ │ (被控端) │
└───────┬───────┘ └───────┬───────┘ └───────────────┘
│ │
│ P2P Connection │
│◄────────────────────────►│
│ │
│ (如果 P2P 失败) │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
└─►│ Relay Server │◄──────┘
│ (hbbr) │
│ Port: 21117 │
└───────────────┘
```
## 服务器组件详解
### Rendezvous Server (hbbs)
**监听端口:**
| 端口 | 协议 | 用途 |
|------|------|------|
| 21116 | TCP | 主要通信端口,处理 punch hole 请求 |
| 21116 | UDP | Peer 注册、NAT 类型检测 |
| 21115 | TCP | NAT 测试专用端口 |
| 21118 | WebSocket | Web 客户端支持 |
**核心数据结构:**
```rust
// rustdesk-server/src/rendezvous_server.rs:64-83
pub struct RendezvousServer {
tcp_punch: Arc<Mutex<HashMap<SocketAddr, Sink>>>, // TCP punch hole 连接
pm: PeerMap, // Peer 映射表
tx: Sender, // 消息发送通道
relay_servers: Arc<RelayServers>, // 可用 Relay 服务器列表
relay_servers0: Arc<RelayServers>, // 原始 Relay 服务器列表
rendezvous_servers: Arc<Vec<String>>, // Rendezvous 服务器列表
inner: Arc<Inner>, // 内部配置
}
struct Inner {
serial: i32, // 配置序列号
version: String, // 软件版本
software_url: String, // 软件更新 URL
mask: Option<Ipv4Network>, // LAN 掩码
local_ip: String, // 本地 IP
sk: Option<sign::SecretKey>, // 服务器签名密钥
}
```
**Peer 数据结构:**
```rust
// rustdesk-server/src/peer.rs:32-42
pub struct Peer {
pub socket_addr: SocketAddr, // 最后注册的地址
pub last_reg_time: Instant, // 最后注册时间
pub guid: Vec<u8>, // 数据库 GUID
pub uuid: Bytes, // 设备 UUID
pub pk: Bytes, // 公钥
pub info: PeerInfo, // Peer 信息
pub reg_pk: (u32, Instant), // 注册频率限制
}
```
### Relay Server (hbbr)
**监听端口:**
| 端口 | 协议 | 用途 |
|------|------|------|
| 21117 | TCP | 主要中转端口 |
| 21119 | WebSocket | Web 客户端支持 |
**核心特性:**
```rust
// rustdesk-server/src/relay_server.rs:40-44
static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // 降级阈值
static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // 检测开始时间(ms)
static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // 限速(bit/s)
static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024);// 总带宽
static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024);// 单连接带宽
```
## 客户端架构
### 核心模块
```
rustdesk/src/
├── rendezvous_mediator.rs # Rendezvous 服务器通信
├── client.rs # 控制端核心逻辑
├── server/
│ ├── mod.rs # 被控端服务
│ ├── connection.rs # 连接处理
│ ├── video_service.rs # 视频服务
│ ├── audio_service.rs # 音频服务
│ └── input_service.rs # 输入服务
├── common.rs # 通用函数(加密、解密)
└── platform/ # 平台特定代码
```
### RendezvousMediator
```rust
// rustdesk/src/rendezvous_mediator.rs:44-50
pub struct RendezvousMediator {
addr: TargetAddr<'static>, // 服务器地址
host: String, // 服务器主机名
host_prefix: String, // 主机前缀
keep_alive: i32, // 保活间隔
}
```
**两种连接模式:**
1. **UDP 模式** (默认):
- 用于 Peer 注册和心跳
- 更低延迟
- 可能被某些防火墙阻止
2. **TCP 模式**:
- 用于代理环境
- WebSocket 模式
- 更可靠
## 连接流程概述
### 被控端启动流程
```
1. 生成设备 ID 和密钥对
2. 连接 Rendezvous Server
3. 发送 RegisterPeer 消息
4. 如果需要,发送 RegisterPk 注册公钥
5. 定期发送心跳保持在线状态
6. 等待 PunchHole 或 RequestRelay 请求
```
### 控制端连接流程
```
1. 输入目标设备 ID
2. 连接 Rendezvous Server
3. 发送 PunchHoleRequest 消息
4. 根据响应决定连接方式:
a. 直连 (P2P): 使用 PunchHole 信息尝试打洞
b. 局域网: 使用 LocalAddr 信息直连
c. 中转: 通过 Relay Server 连接
5. 建立安全加密通道
6. 发送 LoginRequest 进行身份验证
7. 开始远程控制会话
```
## 数据流
### 视频流
```
被控端 控制端
│ │
│ VideoFrame (H264/VP9/...) │
├─────────────────────────────────►│
│ │
│ 加密 → 传输 → 解密 → 解码 → 显示 │
```
### 输入流
```
控制端 被控端
│ │
│ MouseEvent/KeyEvent │
├─────────────────────────────────►│
│ │
│ 加密 → 传输 → 解密 → 模拟输入 │
```
## 高可用设计
### 多服务器支持
- 客户端可配置多个 Rendezvous Server
- 自动选择延迟最低的服务器
- 连接失败时自动切换备用服务器
### Relay Server 选择
- 支持配置多个 Relay Server
- 轮询算法分配负载
- 定期检查 Relay Server 可用性
### 重连机制
```rust
// 连接超时和重试参数
const REG_INTERVAL: i64 = 12_000; // 注册间隔 12 秒
const REG_TIMEOUT: i32 = 30_000; // 注册超时 30 秒
const CONNECT_TIMEOUT: u64 = 18_000; // 连接超时 18 秒
```

View File

@@ -0,0 +1,438 @@
# Rendezvous 服务器协议
## 概述
Rendezvous Serverhbbs是 RustDesk 的核心协调服务器,负责:
- Peer ID 注册和发现
- 公钥存储和分发
- NAT 类型检测
- P2P 连接协调(打洞辅助)
- Relay Server 分配
## 协议消息定义
所有消息使用 Protocol Buffers 定义在 `protos/rendezvous.proto`
```protobuf
message RendezvousMessage {
oneof union {
RegisterPeer register_peer = 6;
RegisterPeerResponse register_peer_response = 7;
PunchHoleRequest punch_hole_request = 8;
PunchHole punch_hole = 9;
PunchHoleSent punch_hole_sent = 10;
PunchHoleResponse punch_hole_response = 11;
FetchLocalAddr fetch_local_addr = 12;
LocalAddr local_addr = 13;
ConfigUpdate configure_update = 14;
RegisterPk register_pk = 15;
RegisterPkResponse register_pk_response = 16;
SoftwareUpdate software_update = 17;
RequestRelay request_relay = 18;
RelayResponse relay_response = 19;
TestNatRequest test_nat_request = 20;
TestNatResponse test_nat_response = 21;
PeerDiscovery peer_discovery = 22;
OnlineRequest online_request = 23;
OnlineResponse online_response = 24;
KeyExchange key_exchange = 25;
HealthCheck hc = 26;
}
}
```
## 核心流程
### 1. Peer 注册流程
**客户端 → 服务器RegisterPeer**
```protobuf
message RegisterPeer {
string id = 1; // Peer ID (如 "123456789")
int32 serial = 2; // 配置序列号
}
```
**服务器处理逻辑:**
```rust
// rustdesk-server/src/rendezvous_server.rs:318-333
Some(rendezvous_message::Union::RegisterPeer(rp)) => {
if !rp.id.is_empty() {
log::trace!("New peer registered: {:?} {:?}", &rp.id, &addr);
self.update_addr(rp.id, addr, socket).await?;
// 如果服务器配置更新,发送 ConfigUpdate
if self.inner.serial > rp.serial {
let mut msg_out = RendezvousMessage::new();
msg_out.set_configure_update(ConfigUpdate {
serial: self.inner.serial,
rendezvous_servers: (*self.rendezvous_servers).clone(),
..Default::default()
});
socket.send(&msg_out, addr).await?;
}
}
}
```
**服务器 → 客户端RegisterPeerResponse**
```protobuf
message RegisterPeerResponse {
bool request_pk = 2; // 是否需要注册公钥
}
```
### 2. 公钥注册流程
当服务器检测到 Peer 的公钥为空或 IP 变化时,会请求注册公钥。
**客户端 → 服务器RegisterPk**
```protobuf
message RegisterPk {
string id = 1; // Peer ID
bytes uuid = 2; // 设备 UUID
bytes pk = 3; // Ed25519 公钥
string old_id = 4; // 旧 ID如果更换
}
```
**服务器处理逻辑:**
```rust
// rustdesk-server/src/rendezvous_server.rs:334-418
Some(rendezvous_message::Union::RegisterPk(rk)) => {
// 验证 UUID 和公钥
if rk.uuid.is_empty() || rk.pk.is_empty() {
return Ok(());
}
let id = rk.id;
let ip = addr.ip().to_string();
// ID 长度检查
if id.len() < 6 {
return send_rk_res(socket, addr, UUID_MISMATCH).await;
}
// IP 封锁检查
if !self.check_ip_blocker(&ip, &id).await {
return send_rk_res(socket, addr, TOO_FREQUENT).await;
}
// UUID 匹配验证
let peer = self.pm.get_or(&id).await;
// ... UUID 验证逻辑 ...
// 更新数据库
if changed {
self.pm.update_pk(id, peer, addr, rk.uuid, rk.pk, ip).await;
}
// 发送成功响应
msg_out.set_register_pk_response(RegisterPkResponse {
result: register_pk_response::Result::OK.into(),
..Default::default()
});
}
```
**服务器 → 客户端RegisterPkResponse**
```protobuf
message RegisterPkResponse {
enum Result {
OK = 0;
UUID_MISMATCH = 2;
ID_EXISTS = 3;
TOO_FREQUENT = 4;
INVALID_ID_FORMAT = 5;
NOT_SUPPORT = 6;
SERVER_ERROR = 7;
}
Result result = 1;
int32 keep_alive = 2; // 心跳间隔
}
```
### 3. Punch Hole 请求流程
当控制端要连接被控端时,首先发送 PunchHoleRequest。
**控制端 → 服务器PunchHoleRequest**
```protobuf
message PunchHoleRequest {
string id = 1; // 目标 Peer ID
NatType nat_type = 2; // 请求方的 NAT 类型
string licence_key = 3; // 许可证密钥
ConnType conn_type = 4; // 连接类型
string token = 5; // 认证令牌
string version = 6; // 客户端版本
}
enum NatType {
UNKNOWN_NAT = 0;
ASYMMETRIC = 1;
SYMMETRIC = 2;
}
enum ConnType {
DEFAULT_CONN = 0;
FILE_TRANSFER = 1;
PORT_FORWARD = 2;
RDP = 3;
VIEW_CAMERA = 4;
}
```
**服务器处理逻辑:**
```rust
// rustdesk-server/src/rendezvous_server.rs:674-765
async fn handle_punch_hole_request(...) {
// 1. 验证许可证密钥
if !key.is_empty() && ph.licence_key != key {
return Ok((PunchHoleResponse { failure: LICENSE_MISMATCH }, None));
}
// 2. 查找目标 Peer
if let Some(peer) = self.pm.get(&id).await {
let (elapsed, peer_addr) = peer.read().await;
// 3. 检查在线状态
if elapsed >= REG_TIMEOUT {
return Ok((PunchHoleResponse { failure: OFFLINE }, None));
}
// 4. 判断是否同一局域网
let same_intranet = (peer_is_lan && is_lan) ||
(peer_addr.ip() == addr.ip());
if same_intranet {
// 请求获取本地地址
msg_out.set_fetch_local_addr(FetchLocalAddr {
socket_addr: AddrMangle::encode(addr).into(),
relay_server,
});
} else {
// 发送 Punch Hole 请求给被控端
msg_out.set_punch_hole(PunchHole {
socket_addr: AddrMangle::encode(addr).into(),
nat_type: ph.nat_type,
relay_server,
});
}
return Ok((msg_out, Some(peer_addr)));
}
// Peer 不存在
Ok((PunchHoleResponse { failure: ID_NOT_EXIST }, None))
}
```
**服务器 → 被控端PunchHole 或 FetchLocalAddr**
```protobuf
message PunchHole {
bytes socket_addr = 1; // 控制端地址(编码)
string relay_server = 2; // Relay 服务器地址
NatType nat_type = 3; // 控制端 NAT 类型
}
message FetchLocalAddr {
bytes socket_addr = 1; // 控制端地址(编码)
string relay_server = 2; // Relay 服务器地址
}
```
### 4. 被控端响应流程
**被控端 → 服务器PunchHoleSent 或 LocalAddr**
```protobuf
message PunchHoleSent {
bytes socket_addr = 1; // 控制端地址
string id = 2; // 被控端 ID
string relay_server = 3; // Relay 服务器
NatType nat_type = 4; // 被控端 NAT 类型
string version = 5; // 客户端版本
}
message LocalAddr {
bytes socket_addr = 1; // 控制端地址
bytes local_addr = 2; // 被控端本地地址
string relay_server = 3; // Relay 服务器
string id = 4; // 被控端 ID
string version = 5; // 客户端版本
}
```
**服务器 → 控制端PunchHoleResponse**
```protobuf
message PunchHoleResponse {
bytes socket_addr = 1; // 被控端地址
bytes pk = 2; // 被控端公钥(已签名)
enum Failure {
ID_NOT_EXIST = 0;
OFFLINE = 2;
LICENSE_MISMATCH = 3;
LICENSE_OVERUSE = 4;
}
Failure failure = 3;
string relay_server = 4;
oneof union {
NatType nat_type = 5;
bool is_local = 6; // 是否为局域网连接
}
string other_failure = 7;
int32 feedback = 8;
}
```
### 5. Relay 请求流程
当 P2P 连接失败或 NAT 类型不支持打洞时,使用 Relay。
**客户端 → 服务器RequestRelay**
```protobuf
message RequestRelay {
string id = 1; // 目标 Peer ID
string uuid = 2; // 连接 UUID用于配对
bytes socket_addr = 3; // 本端地址
string relay_server = 4; // 指定的 Relay 服务器
bool secure = 5; // 是否使用加密
string licence_key = 6; // 许可证密钥
ConnType conn_type = 7; // 连接类型
string token = 8; // 认证令牌
}
```
**服务器 → 客户端RelayResponse**
```protobuf
message RelayResponse {
bytes socket_addr = 1; // 对端地址
string uuid = 2; // 连接 UUID
string relay_server = 3; // Relay 服务器地址
oneof union {
string id = 4; // 对端 ID
bytes pk = 5; // 对端公钥
}
string refuse_reason = 6; // 拒绝原因
string version = 7; // 版本
int32 feedback = 9;
}
```
## NAT 类型检测
**客户端 → 服务器TestNatRequest**
```protobuf
message TestNatRequest {
int32 serial = 1; // 配置序列号
}
```
**服务器 → 客户端TestNatResponse**
```protobuf
message TestNatResponse {
int32 port = 1; // 观测到的源端口
ConfigUpdate cu = 2; // 配置更新
}
```
NAT 检测原理:
1. 客户端同时向主端口21116和 NAT 测试端口21115发送请求
2. 比较两次响应中观测到的源端口
3. 如果端口一致,则为 ASYMMETRIC NAT适合打洞
4. 如果端口不一致,则为 SYMMETRIC NAT需要 Relay
## 地址编码
RustDesk 使用 `AddrMangle` 对 SocketAddr 进行编码:
```rust
// 编码示例
// IPv4: 4 bytes IP + 2 bytes port = 6 bytes
// IPv6: 16 bytes IP + 2 bytes port = 18 bytes
pub fn encode(addr: SocketAddr) -> Vec<u8>;
pub fn decode(bytes: &[u8]) -> SocketAddr;
```
## 安全机制
### 服务器签名
当服务器配置了私钥时,会对 Peer 的公钥进行签名:
```rust
// rustdesk-server/src/rendezvous_server.rs:1160-1182
async fn get_pk(&mut self, version: &str, id: String) -> Bytes {
if version.is_empty() || self.inner.sk.is_none() {
Bytes::new()
} else {
match self.pm.get(&id).await {
Some(peer) => {
let pk = peer.read().await.pk.clone();
// 使用服务器私钥签名 IdPk
sign::sign(
&IdPk { id, pk, ..Default::default() }
.write_to_bytes()
.unwrap_or_default(),
self.inner.sk.as_ref().unwrap(),
).into()
}
_ => Bytes::new(),
}
}
}
```
### IP 封锁
服务器实现了 IP 封锁机制防止滥用:
```rust
// rustdesk-server/src/rendezvous_server.rs:866-894
async fn check_ip_blocker(&self, ip: &str, id: &str) -> bool {
let mut lock = IP_BLOCKER.lock().await;
if let Some(old) = lock.get_mut(ip) {
// 每秒请求超过 30 次则封锁
if counter.0 > 30 {
return false;
}
// 每天超过 300 个不同 ID 则封锁
if counter.0.len() > 300 {
return !is_new;
}
}
true
}
```
## 时序图
### 完整连接建立流程
```
控制端 Rendezvous Server 被控端
│ │ │
│ PunchHoleRequest │ │
├──────────────────────►│ │
│ │ PunchHole │
│ ├──────────────────────►│
│ │ │
│ │ PunchHoleSent │
│ │◄──────────────────────┤
│ PunchHoleResponse │ │
│◄──────────────────────┤ │
│ │ │
│ ─────────── P2P Connection ──────────────────│
│◄─────────────────────────────────────────────►│
```

View File

@@ -0,0 +1,318 @@
# Relay 服务器协议
## 概述
Relay Serverhbbr是 RustDesk 的数据中转服务器,当 P2P 连接无法建立时(如双方都在 Symmetric NAT 后面),所有通信数据通过 Relay Server 转发。
## 服务器架构
### 监听端口
| 端口 | 协议 | 用途 |
|------|------|------|
| 21117 | TCP | 主要中转端口 |
| 21119 | WebSocket | Web 客户端支持 |
### 核心配置
```rust
// rustdesk-server/src/relay_server.rs:40-46
static DOWNGRADE_THRESHOLD_100: AtomicUsize = AtomicUsize::new(66); // 0.66
static DOWNGRADE_START_CHECK: AtomicUsize = AtomicUsize::new(1_800_000); // 30分钟 (ms)
static LIMIT_SPEED: AtomicUsize = AtomicUsize::new(32 * 1024 * 1024); // 32 Mb/s
static TOTAL_BANDWIDTH: AtomicUsize = AtomicUsize::new(1024 * 1024 * 1024);// 1024 Mb/s
static SINGLE_BANDWIDTH: AtomicUsize = AtomicUsize::new(128 * 1024 * 1024);// 128 Mb/s
const BLACKLIST_FILE: &str = "blacklist.txt";
const BLOCKLIST_FILE: &str = "blocklist.txt";
```
## 连接配对机制
### 配对原理
Relay Server 使用 UUID 来配对两个客户端的连接:
1. 第一个客户端连接并发送 `RequestRelay` 消息(包含 UUID
2. 服务器将该连接存储在等待队列中
3. 第二个客户端使用相同的 UUID 连接
4. 服务器将两个连接配对,开始转发数据
### 配对流程
```rust
// rustdesk-server/src/relay_server.rs:425-462
async fn make_pair_(stream: impl StreamTrait, addr: SocketAddr, key: &str, limiter: Limiter) {
let mut stream = stream;
if let Ok(Some(Ok(bytes))) = timeout(30_000, stream.recv()).await {
if let Ok(msg_in) = RendezvousMessage::parse_from_bytes(&bytes) {
if let Some(rendezvous_message::Union::RequestRelay(rf)) = msg_in.union {
// 验证许可证密钥
if !key.is_empty() && rf.licence_key != key {
log::warn!("Relay authentication failed from {}", addr);
return;
}
if !rf.uuid.is_empty() {
// 尝试查找配对
let mut peer = PEERS.lock().await.remove(&rf.uuid);
if let Some(peer) = peer.as_mut() {
// 找到配对,开始中转
log::info!("Relay request {} got paired", rf.uuid);
relay(addr, &mut stream, peer, limiter).await;
} else {
// 没找到,存储等待配对
log::info!("New relay request {} from {}", rf.uuid, addr);
PEERS.lock().await.insert(rf.uuid.clone(), Box::new(stream));
sleep(30.).await; // 等待 30 秒
PEERS.lock().await.remove(&rf.uuid); // 超时移除
}
}
}
}
}
}
```
## 数据转发
### 转发逻辑
```rust
// rustdesk-server/src/relay_server.rs:464-566
async fn relay(
addr: SocketAddr,
stream: &mut impl StreamTrait,
peer: &mut Box<dyn StreamTrait>,
total_limiter: Limiter,
) -> ResultType<()> {
let limiter = <Limiter>::new(SINGLE_BANDWIDTH.load(Ordering::SeqCst) as f64);
let blacklist_limiter = <Limiter>::new(LIMIT_SPEED.load(Ordering::SeqCst) as _);
loop {
tokio::select! {
// 从 peer 接收数据,发送给 stream
res = peer.recv() => {
if let Some(Ok(bytes)) = res {
// 带宽限制
if blacked || downgrade {
blacklist_limiter.consume(bytes.len() * 8).await;
} else {
limiter.consume(bytes.len() * 8).await;
}
total_limiter.consume(bytes.len() * 8).await;
stream.send_raw(bytes.into()).await?;
} else {
break;
}
},
// 从 stream 接收数据,发送给 peer
res = stream.recv() => {
if let Some(Ok(bytes)) = res {
// 带宽限制
limiter.consume(bytes.len() * 8).await;
total_limiter.consume(bytes.len() * 8).await;
peer.send_raw(bytes.into()).await?;
} else {
break;
}
},
_ = timer.tick() => {
// 超时检测
if last_recv_time.elapsed().as_secs() > 30 {
bail!("Timeout");
}
}
}
// 降级检测
if elapsed > DOWNGRADE_START_CHECK && total > elapsed * downgrade_threshold {
downgrade = true;
log::info!("Downgrade {}, exceed threshold", id);
}
}
Ok(())
}
```
### 原始模式
当两端都支持原始模式时,跳过 protobuf 解析以提高性能:
```rust
// rustdesk-server/src/relay_server.rs:440-444
if !stream.is_ws() && !peer.is_ws() {
peer.set_raw();
stream.set_raw();
log::info!("Both are raw");
}
```
## 带宽控制
### 多级限速
1. **总带宽限制**:整个服务器的总带宽
2. **单连接限制**:每个中转连接的带宽
3. **黑名单限速**:对黑名单 IP 的特殊限制
### 降级机制
当连接持续占用高带宽时,会触发降级:
```rust
// 条件:
// 1. 连接时间 > DOWNGRADE_START_CHECK (30分钟)
// 2. 平均带宽 > SINGLE_BANDWIDTH * 0.66
// 降级后使用 LIMIT_SPEED (32 Mb/s) 限速
if elapsed > DOWNGRADE_START_CHECK.load(Ordering::SeqCst)
&& !downgrade
&& total > elapsed * downgrade_threshold
{
downgrade = true;
}
```
## 安全控制
### 黑名单
用于限速特定 IP
```
# blacklist.txt
192.168.1.100
10.0.0.50
```
### 封锁名单
用于完全拒绝特定 IP
```
# blocklist.txt
1.2.3.4
5.6.7.8
```
### 运行时管理命令
通过本地 TCP 连接(仅限 localhost发送命令
```rust
// rustdesk-server/src/relay_server.rs:152-324
match fds.next() {
Some("h") => // 帮助
Some("blacklist-add" | "ba") => // 添加黑名单
Some("blacklist-remove" | "br") => // 移除黑名单
Some("blacklist" | "b") => // 查看黑名单
Some("blocklist-add" | "Ba") => // 添加封锁名单
Some("blocklist-remove" | "Br") => // 移除封锁名单
Some("blocklist" | "B") => // 查看封锁名单
Some("downgrade-threshold" | "dt") => // 设置降级阈值
Some("downgrade-start-check" | "t") => // 设置降级检测时间
Some("limit-speed" | "ls") => // 设置限速
Some("total-bandwidth" | "tb") => // 设置总带宽
Some("single-bandwidth" | "sb") => // 设置单连接带宽
Some("usage" | "u") => // 查看使用统计
}
```
## 协议消息
### RequestRelay
用于建立中转连接的请求消息:
```protobuf
message RequestRelay {
string id = 1; // 目标 Peer ID
string uuid = 2; // 连接 UUID配对用
bytes socket_addr = 3; // 本端地址
string relay_server = 4; // Relay 服务器
bool secure = 5; // 是否加密
string licence_key = 6; // 许可证密钥
ConnType conn_type = 7; // 连接类型
string token = 8; // 认证令牌
}
```
## 时序图
### 中转连接建立
```
客户端 A Relay Server 客户端 B
│ │ │
│ RequestRelay(uuid) │ │
├─────────────────────────►│ │
│ │ │
│ │ (存储等待配对) │
│ │ │
│ │ RequestRelay(uuid) │
│ │◄───────────────────────────┤
│ │ │
│ │ (配对成功) │
│ │ │
│ ◄────────── 数据转发 ─────────────────────────────────►│
│ │ │
```
### 数据转发
```
客户端 A Relay Server 客户端 B
│ │ │
│ ────[数据]───────► │ │
│ │ ────[数据]───────► │
│ │ │
│ │ ◄───[数据]──────── │
│ ◄───[数据]──────── │ │
│ │ │
```
## 性能优化
### 零拷贝
使用 `Bytes` 类型减少内存拷贝:
```rust
async fn send_raw(&mut self, bytes: Bytes) -> ResultType<()>;
```
### WebSocket 支持
支持 WebSocket 协议以穿越防火墙:
```rust
#[async_trait]
impl StreamTrait for tokio_tungstenite::WebSocketStream<TcpStream> {
async fn recv(&mut self) -> Option<Result<BytesMut, Error>> {
if let Some(msg) = self.next().await {
match msg {
Ok(tungstenite::Message::Binary(bytes)) => {
Some(Ok(bytes[..].into()))
}
// ...
}
}
}
}
```
## 监控指标
服务器跟踪以下指标:
| 指标 | 说明 |
|------|------|
| elapsed | 连接持续时间 (ms) |
| total | 总传输数据量 (bit) |
| highest | 最高瞬时速率 (kb/s) |
| speed | 当前速率 (kb/s) |
通过 `usage` 命令查看:
```
192.168.1.100:12345: 3600s 1024.00MB 50000kb/s 45000kb/s 42000kb/s
```

View File

@@ -0,0 +1,424 @@
# P2P 连接流程
## 概述
RustDesk 优先尝试建立 P2P 直连,只有在直连失败时才使用 Relay 中转。P2P 连接支持多种方式:
- TCP 打洞
- UDP 打洞KCP
- 局域网直连
- IPv6 直连
## 连接决策流程
```
开始连接
┌──────────────┐
│ 是否强制 Relay
└──────┬───────┘
是 │ 否
┌─────────┴─────────┐
▼ ▼
使用 Relay 检查 NAT 类型
┌──────────────┴──────────────┐
│ │
▼ ▼
双方都是对称 NAT 有一方是可穿透 NAT
│ │
是 │ │
▼ ▼
使用 Relay 尝试 P2P 连接
┌─────────────┴─────────────┐
│ │
▼ ▼
同一局域网? 不同网络
│ │
是 │ │
▼ ▼
局域网直连 尝试打洞
┌──────────────┴──────────────┐
│ │
▼ ▼
TCP 打洞成功? UDP 打洞成功?
│ │
是 │ 否 是 │ 否
▼ │ ▼ │
TCP P2P 连接 └───────────► KCP P2P 连接 │
使用 Relay
```
## 客户端连接入口
```rust
// rustdesk/src/client.rs:188-230
impl Client {
pub async fn start(
peer: &str,
key: &str,
token: &str,
conn_type: ConnType,
interface: impl Interface,
) -> ResultType<...> {
// 检查是否为 IP 直连
if hbb_common::is_ip_str(peer) {
return connect_tcp_local(check_port(peer, RELAY_PORT + 1), None, CONNECT_TIMEOUT).await;
}
// 检查是否为域名:端口格式
if hbb_common::is_domain_port_str(peer) {
return connect_tcp_local(peer, None, CONNECT_TIMEOUT).await;
}
// 通过 Rendezvous Server 连接
let (rendezvous_server, servers, _) = crate::get_rendezvous_server(1_000).await;
Self::_start_inner(peer, key, token, conn_type, interface, rendezvous_server, servers).await
}
}
```
## 被控端处理连接请求
### 处理 PunchHole 消息
```rust
// rustdesk/src/rendezvous_mediator.rs:554-619
async fn handle_punch_hole(&self, ph: PunchHole, server: ServerPtr) -> ResultType<()> {
let peer_addr = AddrMangle::decode(&ph.socket_addr);
let relay_server = self.get_relay_server(ph.relay_server);
// 判断是否需要 Relay
if ph.nat_type.enum_value() == Ok(NatType::SYMMETRIC)
|| Config::get_nat_type() == NatType::SYMMETRIC as i32
|| relay
{
// 使用 Relay
let uuid = Uuid::new_v4().to_string();
return self.create_relay(ph.socket_addr, relay_server, uuid, server, true, true).await;
}
// 尝试 UDP 打洞
if ph.udp_port > 0 {
peer_addr.set_port(ph.udp_port as u16);
self.punch_udp_hole(peer_addr, server, msg_punch).await?;
return Ok(());
}
// 尝试 TCP 打洞
log::debug!("Punch tcp hole to {:?}", peer_addr);
let socket = {
let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;
let local_addr = socket.local_addr();
// 关键步骤:尝试连接对方,让 NAT 建立映射
allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await);
socket
};
// 发送 PunchHoleSent 告知 Rendezvous Server
let mut msg_out = Message::new();
msg_out.set_punch_hole_sent(PunchHoleSent {
socket_addr: ph.socket_addr,
id: Config::get_id(),
relay_server,
nat_type: nat_type.into(),
version: crate::VERSION.to_owned(),
});
socket.send_raw(msg_out.write_to_bytes()?).await?;
// 接受控制端连接
crate::accept_connection(server.clone(), socket, peer_addr, true).await;
Ok(())
}
```
### 处理 FetchLocalAddr局域网连接
```rust
// rustdesk/src/rendezvous_mediator.rs:481-552
async fn handle_intranet(&self, fla: FetchLocalAddr, server: ServerPtr) -> ResultType<()> {
let peer_addr = AddrMangle::decode(&fla.socket_addr);
let relay_server = self.get_relay_server(fla.relay_server.clone());
// 尝试局域网直连
if is_ipv4(&self.addr) && !relay && !config::is_disable_tcp_listen() {
if let Err(err) = self.handle_intranet_(fla.clone(), server.clone(), relay_server.clone()).await {
log::debug!("Failed to handle intranet: {:?}, will try relay", err);
} else {
return Ok(());
}
}
// 局域网直连失败,使用 Relay
let uuid = Uuid::new_v4().to_string();
self.create_relay(fla.socket_addr, relay_server, uuid, server, true, true).await
}
async fn handle_intranet_(&self, fla: FetchLocalAddr, server: ServerPtr, relay_server: String) -> ResultType<()> {
let peer_addr = AddrMangle::decode(&fla.socket_addr);
let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;
let local_addr = socket.local_addr();
// 发送本地地址给 Rendezvous Server
let mut msg_out = Message::new();
msg_out.set_local_addr(LocalAddr {
id: Config::get_id(),
socket_addr: AddrMangle::encode(peer_addr).into(),
local_addr: AddrMangle::encode(local_addr).into(),
relay_server,
version: crate::VERSION.to_owned(),
});
socket.send_raw(msg_out.write_to_bytes()?).await?;
// 接受连接
crate::accept_connection(server.clone(), socket, peer_addr, true).await;
Ok(())
}
```
## UDP 打洞 (KCP)
### 打洞原理
UDP 打洞利用 NAT 的端口映射特性:
1. A 向 Rendezvous Server 注册NAT 创建映射 `A_internal:port1 → A_external:port2`
2. B 同样注册,创建映射 `B_internal:port3 → B_external:port4`
3. A 向 B 的外部地址发送 UDP 包A 的 NAT 创建到 B 的映射
4. B 向 A 的外部地址发送 UDP 包B 的 NAT 创建到 A 的映射
5. 如果 NAT 不是 Symmetric 类型,双方的包可以到达对方
```rust
// rustdesk/src/rendezvous_mediator.rs:621-642
async fn punch_udp_hole(
&self,
peer_addr: SocketAddr,
server: ServerPtr,
msg_punch: PunchHoleSent,
) -> ResultType<()> {
let mut msg_out = Message::new();
msg_out.set_punch_hole_sent(msg_punch);
let (socket, addr) = new_direct_udp_for(&self.host).await?;
let data = msg_out.write_to_bytes()?;
// 发送到 Rendezvous Server
socket.send_to(&data, addr).await?;
// 多次尝试发送以增加成功率
let socket_cloned = socket.clone();
tokio::spawn(async move {
for _ in 0..2 {
let tm = (hbb_common::time_based_rand() % 20 + 10) as f32 / 1000.;
hbb_common::sleep(tm).await;
socket.send_to(&data, addr).await.ok();
}
});
// 等待对方连接
udp_nat_listen(socket_cloned.clone(), peer_addr, peer_addr, server).await?;
Ok(())
}
```
### KCP 协议
RustDesk 在 UDP 上使用 KCP 协议提供可靠传输:
```rust
// rustdesk/src/rendezvous_mediator.rs:824-851
async fn udp_nat_listen(
socket: Arc<tokio::net::UdpSocket>,
peer_addr: SocketAddr,
peer_addr_v4: SocketAddr,
server: ServerPtr,
) -> ResultType<()> {
socket.connect(peer_addr).await?;
// 执行 UDP 打洞
let res = crate::punch_udp(socket.clone(), true).await?;
// 建立 KCP 流
let stream = crate::kcp_stream::KcpStream::accept(
socket,
Duration::from_millis(CONNECT_TIMEOUT as _),
res,
).await?;
// 创建连接
crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true).await?;
Ok(())
}
```
## TCP 打洞
### 原理
TCP 打洞比 UDP 更难,因为 TCP 需要三次握手。基本思路:
1. A 和 B 都尝试同时向对方发起连接
2. 第一个 SYN 包会被对方的 NAT 丢弃(因为没有映射)
3. 但这个 SYN 包会在 A 的 NAT 上创建映射
4. 当 B 的 SYN 包到达 A 的 NAT 时,由于已有映射,会被转发给 A
5. 连接建立
### 实现
```rust
// rustdesk/src/rendezvous_mediator.rs:604-617
log::debug!("Punch tcp hole to {:?}", peer_addr);
let mut socket = {
let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;
let local_addr = socket.local_addr();
// 关键:使用相同的本地地址尝试连接对方
// 这会在 NAT 上创建映射,使对方的连接请求能够到达
allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await);
socket
};
```
## Relay 连接
当 P2P 失败时,使用 Relay
```rust
// rustdesk/src/rendezvous_mediator.rs:434-479
async fn create_relay(
&self,
socket_addr: Vec<u8>,
relay_server: String,
uuid: String,
server: ServerPtr,
secure: bool,
initiate: bool,
) -> ResultType<()> {
let peer_addr = AddrMangle::decode(&socket_addr);
log::info!(
"create_relay requested from {:?}, relay_server: {}, uuid: {}, secure: {}",
peer_addr, relay_server, uuid, secure,
);
// 连接 Rendezvous Server 发送 RelayResponse
let mut socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;
let mut msg_out = Message::new();
let mut rr = RelayResponse {
socket_addr: socket_addr.into(),
version: crate::VERSION.to_owned(),
..Default::default()
};
if initiate {
rr.uuid = uuid.clone();
rr.relay_server = relay_server.clone();
rr.set_id(Config::get_id());
}
msg_out.set_relay_response(rr);
socket.send(&msg_out).await?;
// 连接 Relay Server
crate::create_relay_connection(
server,
relay_server,
uuid,
peer_addr,
secure,
is_ipv4(&self.addr),
).await;
Ok(())
}
```
## IPv6 支持
RustDesk 优先尝试 IPv6 连接:
```rust
// rustdesk/src/rendezvous_mediator.rs:808-822
async fn start_ipv6(
peer_addr_v6: SocketAddr,
peer_addr_v4: SocketAddr,
server: ServerPtr,
) -> bytes::Bytes {
crate::test_ipv6().await;
if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await {
let server = server.clone();
tokio::spawn(async move {
allow_err!(udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server).await);
});
return local_addr_v6;
}
Default::default()
}
```
## 连接状态机
```
┌─────────────────────────────────────────┐
│ │
▼ │
┌───────────┐ ┌────┴────┐
│ 等待连接 │──────PunchHoleRequest──────►│正在连接 │
└───────────┘ └────┬────┘
┌──────────────────────────────┼──────────────────────────────┐
│ │ │
▼ ▼ ▼
┌────────────┐ ┌─────────────┐ ┌─────────────┐
│ P2P TCP │ │ P2P UDP/KCP │ │ Relay │
│ 连接中 │ │ 连接中 │ │ 连接中 │
└─────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
成功 │ 失败 成功 │ 失败 成功 │ 失败
│ │ │ │ │ │
▼ │ ▼ │ ▼ │
┌──────────┐│ ┌──────────┐│ ┌──────────┐│
│已连接 ││ │已连接 ││ │已连接 ││
│(直连) ││ │(UDP) ││ │(中转) ││
└──────────┘│ └──────────┘│ └──────────┘│
│ │ │
└──────────────►尝试 Relay◄───┘ │
│ │
└────────────────────────────────────────┘
```
## 直接连接模式
用户可以配置允许直接 TCP 连接(不经过 Rendezvous Server
```rust
// rustdesk/src/rendezvous_mediator.rs:727-792
async fn direct_server(server: ServerPtr) {
let mut listener = None;
let mut port = get_direct_port(); // 默认 21118
loop {
let disabled = !option2bool(OPTION_DIRECT_SERVER, &Config::get_option(OPTION_DIRECT_SERVER));
if !disabled && listener.is_none() {
match hbb_common::tcp::listen_any(port as _).await {
Ok(l) => {
listener = Some(l);
log::info!("Direct server listening on: {:?}", l.local_addr());
}
Err(err) => {
log::error!("Failed to start direct server: {}", err);
}
}
}
if let Some(l) = listener.as_mut() {
if let Ok(Ok((stream, addr))) = hbb_common::timeout(1000, l.accept()).await {
stream.set_nodelay(true).ok();
log::info!("direct access from {}", addr);
let server = server.clone();
tokio::spawn(async move {
crate::server::create_tcp_connection(server, stream, addr, false).await
});
}
}
}
}
```

View File

@@ -0,0 +1,574 @@
# 消息格式定义
## 概述
RustDesk 使用 Protocol Buffers (protobuf) 定义所有网络消息格式。主要有两个 proto 文件:
- `rendezvous.proto` - Rendezvous/Relay 服务器通信消息
- `message.proto` - 客户端之间通信消息
## Rendezvous 消息 (rendezvous.proto)
### 顶层消息
```protobuf
message RendezvousMessage {
oneof union {
RegisterPeer register_peer = 6;
RegisterPeerResponse register_peer_response = 7;
PunchHoleRequest punch_hole_request = 8;
PunchHole punch_hole = 9;
PunchHoleSent punch_hole_sent = 10;
PunchHoleResponse punch_hole_response = 11;
FetchLocalAddr fetch_local_addr = 12;
LocalAddr local_addr = 13;
ConfigUpdate configure_update = 14;
RegisterPk register_pk = 15;
RegisterPkResponse register_pk_response = 16;
SoftwareUpdate software_update = 17;
RequestRelay request_relay = 18;
RelayResponse relay_response = 19;
TestNatRequest test_nat_request = 20;
TestNatResponse test_nat_response = 21;
PeerDiscovery peer_discovery = 22;
OnlineRequest online_request = 23;
OnlineResponse online_response = 24;
KeyExchange key_exchange = 25;
HealthCheck hc = 26;
}
}
```
### 注册相关
```protobuf
// Peer 注册
message RegisterPeer {
string id = 1; // Peer ID
int32 serial = 2; // 配置序列号
}
message RegisterPeerResponse {
bool request_pk = 2; // 是否需要注册公钥
}
// 公钥注册
message RegisterPk {
string id = 1; // Peer ID
bytes uuid = 2; // 设备 UUID
bytes pk = 3; // Ed25519 公钥
string old_id = 4; // 旧 ID
}
message RegisterPkResponse {
enum Result {
OK = 0;
UUID_MISMATCH = 2;
ID_EXISTS = 3;
TOO_FREQUENT = 4;
INVALID_ID_FORMAT = 5;
NOT_SUPPORT = 6;
SERVER_ERROR = 7;
}
Result result = 1;
int32 keep_alive = 2;
}
```
### 连接协调相关
```protobuf
// 连接类型
enum ConnType {
DEFAULT_CONN = 0;
FILE_TRANSFER = 1;
PORT_FORWARD = 2;
RDP = 3;
VIEW_CAMERA = 4;
}
// NAT 类型
enum NatType {
UNKNOWN_NAT = 0;
ASYMMETRIC = 1; // 可打洞
SYMMETRIC = 2; // 需要中转
}
// Punch Hole 请求
message PunchHoleRequest {
string id = 1; // 目标 Peer ID
NatType nat_type = 2;
string licence_key = 3;
ConnType conn_type = 4;
string token = 5;
string version = 6;
}
// Punch Hole 响应
message PunchHoleResponse {
bytes socket_addr = 1; // 目标地址
bytes pk = 2; // 公钥(已签名)
enum Failure {
ID_NOT_EXIST = 0;
OFFLINE = 2;
LICENSE_MISMATCH = 3;
LICENSE_OVERUSE = 4;
}
Failure failure = 3;
string relay_server = 4;
oneof union {
NatType nat_type = 5;
bool is_local = 6;
}
string other_failure = 7;
int32 feedback = 8;
}
// 服务器转发给被控端
message PunchHole {
bytes socket_addr = 1; // 控制端地址
string relay_server = 2;
NatType nat_type = 3;
}
// 被控端发送给服务器
message PunchHoleSent {
bytes socket_addr = 1;
string id = 2;
string relay_server = 3;
NatType nat_type = 4;
string version = 5;
}
```
### Relay 相关
```protobuf
// Relay 请求
message RequestRelay {
string id = 1;
string uuid = 2; // 配对 UUID
bytes socket_addr = 3;
string relay_server = 4;
bool secure = 5;
string licence_key = 6;
ConnType conn_type = 7;
string token = 8;
}
// Relay 响应
message RelayResponse {
bytes socket_addr = 1;
string uuid = 2;
string relay_server = 3;
oneof union {
string id = 4;
bytes pk = 5;
}
string refuse_reason = 6;
string version = 7;
int32 feedback = 9;
}
```
## 会话消息 (message.proto)
### 顶层消息
```protobuf
message Message {
oneof union {
SignedId signed_id = 3;
PublicKey public_key = 4;
TestDelay test_delay = 5;
VideoFrame video_frame = 6;
LoginRequest login_request = 7;
LoginResponse login_response = 8;
Hash hash = 9;
MouseEvent mouse_event = 10;
AudioFrame audio_frame = 11;
CursorData cursor_data = 12;
CursorPosition cursor_position = 13;
uint64 cursor_id = 14;
KeyEvent key_event = 15;
Clipboard clipboard = 16;
FileAction file_action = 17;
FileResponse file_response = 18;
Misc misc = 19;
Cliprdr cliprdr = 20;
MessageBox message_box = 21;
SwitchSidesResponse switch_sides_response = 22;
VoiceCallRequest voice_call_request = 23;
VoiceCallResponse voice_call_response = 24;
PeerInfo peer_info = 25;
PointerDeviceEvent pointer_device_event = 26;
Auth2FA auth_2fa = 27;
MultiClipboards multi_clipboards = 28;
}
}
```
### 认证相关
```protobuf
// ID 和公钥
message IdPk {
string id = 1;
bytes pk = 2;
}
// 密钥交换
message PublicKey {
bytes asymmetric_value = 1; // X25519 公钥
bytes symmetric_value = 2; // 加密的对称密钥
}
// 签名的 ID
message SignedId {
bytes id = 1; // 签名的 IdPk
}
// 密码哈希挑战
message Hash {
string salt = 1;
string challenge = 2;
}
// 登录请求
message LoginRequest {
string username = 1;
bytes password = 2; // 加密的密码
string my_id = 4;
string my_name = 5;
OptionMessage option = 6;
oneof union {
FileTransfer file_transfer = 7;
PortForward port_forward = 8;
ViewCamera view_camera = 15;
}
bool video_ack_required = 9;
uint64 session_id = 10;
string version = 11;
OSLogin os_login = 12;
string my_platform = 13;
bytes hwid = 14;
}
// 登录响应
message LoginResponse {
oneof union {
string error = 1;
PeerInfo peer_info = 2;
}
bool enable_trusted_devices = 3;
}
// 2FA 认证
message Auth2FA {
string code = 1;
bytes hwid = 2;
}
```
### 视频相关
```protobuf
// 编码后的视频帧
message EncodedVideoFrame {
bytes data = 1;
bool key = 2; // 是否关键帧
int64 pts = 3; // 时间戳
}
message EncodedVideoFrames {
repeated EncodedVideoFrame frames = 1;
}
// 视频帧
message VideoFrame {
oneof union {
EncodedVideoFrames vp9s = 6;
RGB rgb = 7;
YUV yuv = 8;
EncodedVideoFrames h264s = 10;
EncodedVideoFrames h265s = 11;
EncodedVideoFrames vp8s = 12;
EncodedVideoFrames av1s = 13;
}
int32 display = 14; // 显示器索引
}
// 显示信息
message DisplayInfo {
sint32 x = 1;
sint32 y = 2;
int32 width = 3;
int32 height = 4;
string name = 5;
bool online = 6;
bool cursor_embedded = 7;
Resolution original_resolution = 8;
double scale = 9;
}
```
### 输入相关
```protobuf
// 鼠标事件
message MouseEvent {
int32 mask = 1; // 按钮掩码
sint32 x = 2;
sint32 y = 3;
repeated ControlKey modifiers = 4;
}
// 键盘事件
message KeyEvent {
bool down = 1; // 按下/释放
bool press = 2; // 单击
oneof union {
ControlKey control_key = 3;
uint32 chr = 4; // 字符码
uint32 unicode = 5; // Unicode
string seq = 6; // 字符序列
uint32 win2win_hotkey = 7;
}
repeated ControlKey modifiers = 8;
KeyboardMode mode = 9;
}
// 键盘模式
enum KeyboardMode {
Legacy = 0;
Map = 1;
Translate = 2;
Auto = 3;
}
// 控制键枚举(部分)
enum ControlKey {
Unknown = 0;
Alt = 1;
Backspace = 2;
CapsLock = 3;
Control = 4;
Delete = 5;
// ... 更多按键
CtrlAltDel = 100;
LockScreen = 101;
}
```
### 音频相关
```protobuf
// 音频格式
message AudioFormat {
uint32 sample_rate = 1;
uint32 channels = 2;
}
// 音频帧
message AudioFrame {
bytes data = 1; // Opus 编码数据
}
```
### 剪贴板相关
```protobuf
// 剪贴板格式
enum ClipboardFormat {
Text = 0;
Rtf = 1;
Html = 2;
ImageRgba = 21;
ImagePng = 22;
ImageSvg = 23;
Special = 31;
}
// 剪贴板内容
message Clipboard {
bool compress = 1;
bytes content = 2;
int32 width = 3;
int32 height = 4;
ClipboardFormat format = 5;
string special_name = 6;
}
message MultiClipboards {
repeated Clipboard clipboards = 1;
}
```
### 文件传输相关
```protobuf
// 文件操作
message FileAction {
oneof union {
ReadDir read_dir = 1;
FileTransferSendRequest send = 2;
FileTransferReceiveRequest receive = 3;
FileDirCreate create = 4;
FileRemoveDir remove_dir = 5;
FileRemoveFile remove_file = 6;
ReadAllFiles all_files = 7;
FileTransferCancel cancel = 8;
FileTransferSendConfirmRequest send_confirm = 9;
FileRename rename = 10;
ReadEmptyDirs read_empty_dirs = 11;
}
}
// 文件响应
message FileResponse {
oneof union {
FileDirectory dir = 1;
FileTransferBlock block = 2;
FileTransferError error = 3;
FileTransferDone done = 4;
FileTransferDigest digest = 5;
ReadEmptyDirsResponse empty_dirs = 6;
}
}
// 文件传输块
message FileTransferBlock {
int32 id = 1;
sint32 file_num = 2;
bytes data = 3;
bool compressed = 4;
uint32 blk_id = 5;
}
// 文件条目
message FileEntry {
FileType entry_type = 1;
string name = 2;
bool is_hidden = 3;
uint64 size = 4;
uint64 modified_time = 5;
}
```
### 杂项消息
```protobuf
message Misc {
oneof union {
ChatMessage chat_message = 4;
SwitchDisplay switch_display = 5;
PermissionInfo permission_info = 6;
OptionMessage option = 7;
AudioFormat audio_format = 8;
string close_reason = 9;
bool refresh_video = 10;
bool video_received = 12;
BackNotification back_notification = 13;
bool restart_remote_device = 14;
// ... 更多选项
}
}
// Peer 信息
message PeerInfo {
string username = 1;
string hostname = 2;
string platform = 3;
repeated DisplayInfo displays = 4;
int32 current_display = 5;
bool sas_enabled = 6;
string version = 7;
Features features = 9;
SupportedEncoding encoding = 10;
SupportedResolutions resolutions = 11;
string platform_additions = 12;
WindowsSessions windows_sessions = 13;
}
// 选项消息
message OptionMessage {
enum BoolOption {
NotSet = 0;
No = 1;
Yes = 2;
}
ImageQuality image_quality = 1;
BoolOption lock_after_session_end = 2;
BoolOption show_remote_cursor = 3;
BoolOption privacy_mode = 4;
BoolOption block_input = 5;
int32 custom_image_quality = 6;
BoolOption disable_audio = 7;
BoolOption disable_clipboard = 8;
BoolOption enable_file_transfer = 9;
SupportedDecoding supported_decoding = 10;
int32 custom_fps = 11;
// ... 更多选项
}
```
## 消息编码
### 长度前缀
TCP 传输时使用长度前缀编码:
```rust
// hbb_common/src/bytes_codec.rs
pub struct BytesCodec {
state: DecodeState,
raw: bool,
}
impl Decoder for BytesCodec {
type Item = BytesMut;
type Error = std::io::Error;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, Self::Error> {
if self.raw {
// 原始模式:直接返回数据
if buf.is_empty() {
Ok(None)
} else {
Ok(Some(buf.split()))
}
} else {
// 标准模式4 字节长度前缀 + 数据
match self.state {
DecodeState::Head => {
if buf.len() < 4 {
return Ok(None);
}
let len = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]) as usize;
buf.advance(4);
self.state = DecodeState::Data(len);
self.decode(buf)
}
DecodeState::Data(len) => {
if buf.len() < len {
return Ok(None);
}
let data = buf.split_to(len);
self.state = DecodeState::Head;
Ok(Some(data))
}
}
}
}
}
```
### 加密模式
当启用加密时,消息结构为:
```
┌─────────────┬─────────────┬─────────────────────────┐
│ Length(4) │ Nonce(8) │ Encrypted Data(N) │
└─────────────┴─────────────┴─────────────────────────┘
```

View File

@@ -0,0 +1,342 @@
# 加密机制
## 概述
RustDesk 使用 libsodium (sodiumoxide) 库实现端到端加密,主要包含:
- **Ed25519**: 用于身份签名和验证
- **X25519**: 用于密钥交换
- **ChaCha20-Poly1305**: 用于对称加密
## 密钥类型
### 1. 身份密钥对 (Ed25519)
用于 Peer 身份认证和签名:
```rust
// 生成密钥对
use sodiumoxide::crypto::sign;
let (pk, sk) = sign::gen_keypair();
// pk: sign::PublicKey (32 bytes)
// sk: sign::SecretKey (64 bytes)
```
### 2. 服务器签名密钥
Rendezvous Server 可以配置签名密钥,用于签名 Peer 公钥:
```rust
// rustdesk-server/src/rendezvous_server.rs:1185-1210
fn get_server_sk(key: &str) -> (String, Option<sign::SecretKey>) {
let mut out_sk = None;
let mut key = key.to_owned();
// 如果是 base64 编码的私钥
if let Ok(sk) = base64::decode(&key) {
if sk.len() == sign::SECRETKEYBYTES {
log::info!("The key is a crypto private key");
key = base64::encode(&sk[(sign::SECRETKEYBYTES / 2)..]); // 公钥部分
let mut tmp = [0u8; sign::SECRETKEYBYTES];
tmp[..].copy_from_slice(&sk);
out_sk = Some(sign::SecretKey(tmp));
}
}
// 如果是占位符,生成新密钥对
if key.is_empty() || key == "-" || key == "_" {
let (pk, sk) = crate::common::gen_sk(0);
out_sk = sk;
if !key.is_empty() {
key = pk;
}
}
if !key.is_empty() {
log::info!("Key: {}", key);
}
(key, out_sk)
}
```
### 3. 会话密钥 (X25519 + ChaCha20)
用于客户端之间的加密通信:
```rust
// hbb_common/src/tcp.rs:27-28
#[derive(Clone)]
pub struct Encrypt(pub Key, pub u64, pub u64);
// Key: secretbox::Key (32 bytes)
// u64: 发送计数器
// u64: 接收计数器
```
## 密钥交换流程
### 1. 身份验证
客户端首先交换签名的身份:
```protobuf
message IdPk {
string id = 1; // Peer ID
bytes pk = 2; // Ed25519 公钥
}
message SignedId {
bytes id = 1; // 签名的 IdPk (by server or self)
}
```
### 2. X25519 密钥交换
使用 X25519 ECDH 生成共享密钥:
```rust
// 生成临时密钥对
use sodiumoxide::crypto::box_;
let (our_pk, our_sk) = box_::gen_keypair();
// 计算共享密钥
let shared_secret = box_::curve25519xsalsa20poly1305::scalarmult(&our_sk, &their_pk);
// 派生对称密钥
let symmetric_key = secretbox::Key::from_slice(&shared_secret[..32]).unwrap();
```
### 3. 对称密钥消息
```protobuf
message PublicKey {
bytes asymmetric_value = 1; // X25519 公钥
bytes symmetric_value = 2; // 加密的对称密钥(用于额外安全)
}
```
## 会话加密
### 加密实现
```rust
// hbb_common/src/tcp.rs
impl Encrypt {
pub fn new(key: Key) -> Self {
Self(key, 0, 0) // 初始化计数器为 0
}
// 加密
pub fn enc(&mut self, data: &[u8]) -> Vec<u8> {
self.1 += 1; // 递增发送计数器
let nonce = self.get_nonce(self.1);
let encrypted = secretbox::seal(data, &nonce, &self.0);
// 格式: nonce (8 bytes) + encrypted data
let mut result = Vec::with_capacity(8 + encrypted.len());
result.extend_from_slice(&self.1.to_le_bytes());
result.extend_from_slice(&encrypted);
result
}
// 解密
pub fn dec(&mut self, data: &mut BytesMut) -> io::Result<()> {
if data.len() < 8 + secretbox::MACBYTES {
return Err(io::Error::new(io::ErrorKind::InvalidData, "too short"));
}
// 提取 nonce
let counter = u64::from_le_bytes(data[..8].try_into().unwrap());
// 防重放攻击检查
if counter <= self.2 {
return Err(io::Error::new(io::ErrorKind::InvalidData, "replay attack"));
}
self.2 = counter;
let nonce = self.get_nonce(counter);
let plaintext = secretbox::open(&data[8..], &nonce, &self.0)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "decrypt failed"))?;
data.clear();
data.extend_from_slice(&plaintext);
Ok(())
}
fn get_nonce(&self, counter: u64) -> Nonce {
let mut nonce = [0u8; 24];
nonce[..8].copy_from_slice(&counter.to_le_bytes());
Nonce(nonce)
}
}
```
### 消息格式
加密后的消息结构:
```
┌──────────────────┬─────────────────────────────────────────┐
│ Counter (8B) │ Encrypted Data + MAC (N+16 bytes) │
└──────────────────┴─────────────────────────────────────────┘
```
## 密码验证
### 挑战-响应机制
被控端生成随机盐和挑战,控制端计算哈希响应:
```protobuf
message Hash {
string salt = 1; // 随机盐
string challenge = 2; // 随机挑战
}
```
### 密码处理
```rust
// 客户端计算密码哈希
fn get_password_hash(password: &str, salt: &str) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(password.as_bytes());
hasher.update(salt.as_bytes());
hasher.finalize().to_vec()
}
// 发送加密的密码(使用对称密钥加密)
fn encrypt_password(password_hash: &[u8], symmetric_key: &Key) -> Vec<u8> {
secretbox::seal(password_hash, &nonce, symmetric_key)
}
```
## 服务器公钥验证
### 签名验证
如果 Rendezvous Server 配置了密钥,会签名 Peer 公钥:
```rust
// 服务器签名 IdPk
let signed_id_pk = sign::sign(
&IdPk { id, pk, ..Default::default() }
.write_to_bytes()?,
&server_sk,
);
// 客户端验证
fn verify_server_signature(signed_pk: &[u8], server_pk: &sign::PublicKey) -> Option<IdPk> {
if let Ok(verified) = sign::verify(signed_pk, server_pk) {
return IdPk::parse_from_bytes(&verified).ok();
}
None
}
```
### 客户端获取服务器公钥
```rust
pub async fn get_rs_pk(id: &str) -> ResultType<(String, sign::PublicKey)> {
// 从配置或 Rendezvous Server 获取公钥
let key = Config::get_option("key");
if !key.is_empty() {
if let Ok(pk) = base64::decode(&key) {
if pk.len() == sign::PUBLICKEYBYTES {
return Ok((key, sign::PublicKey::from_slice(&pk).unwrap()));
}
}
}
// ... 从服务器获取
}
```
## TCP 连接加密
### 安全 TCP 握手
```rust
// rustdesk/src/common.rs
pub async fn secure_tcp(conn: &mut Stream, key: &str) -> ResultType<()> {
// 1. 生成临时 X25519 密钥对
let (our_pk, our_sk) = box_::gen_keypair();
// 2. 发送我们的公钥
let mut msg = Message::new();
msg.set_public_key(PublicKey {
asymmetric_value: our_pk.0.to_vec().into(),
..Default::default()
});
conn.send(&msg).await?;
// 3. 接收对方公钥
let msg = conn.next_timeout(CONNECT_TIMEOUT).await?
.ok_or_else(|| anyhow!("timeout"))?;
let their_pk = msg.get_public_key();
// 4. 计算共享密钥
let shared = box_::curve25519xsalsa20poly1305::scalarmult(
&our_sk,
&box_::PublicKey::from_slice(&their_pk.asymmetric_value)?,
);
// 5. 设置加密
conn.set_key(secretbox::Key::from_slice(&shared[..32]).unwrap());
Ok(())
}
```
## 安全特性
### 1. 前向保密
每个会话使用临时密钥对,即使长期密钥泄露,历史会话仍然安全。
### 2. 重放攻击防护
使用递增计数器作为 nonce 的一部分,拒绝旧的或重复的消息。
### 3. 中间人攻击防护
- 服务器签名 Peer 公钥
- 可配置服务器公钥验证
### 4. 密码暴力破解防护
- 使用盐和多次哈希
- 服务器端限流
## 加密算法参数
| 算法 | 密钥大小 | Nonce 大小 | MAC 大小 |
|------|----------|------------|----------|
| Ed25519 | 64 bytes (private), 32 bytes (public) | N/A | 64 bytes |
| X25519 | 32 bytes | N/A | N/A |
| ChaCha20-Poly1305 | 32 bytes | 24 bytes | 16 bytes |
## 密钥生命周期
```
┌─────────────────────────────────────────────────────────────┐
│ 长期密钥 (Ed25519) │
│ ┌─────────────────┐ │
│ │ 设备首次启动时生成 │ │
│ │ 存储在配置文件中 │ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 会话密钥 (X25519) │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 每次连接时生成 │───►│ 用于密钥协商 │ │
│ │ 临时密钥对 │ │ 派生对称密钥 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 对称密钥 (ChaCha20-Poly1305) │ │
│ │ 用于会话中的所有消息加密 │ │
│ │ 会话结束时销毁 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```

View File

@@ -0,0 +1,410 @@
# NAT 穿透技术
## 概述
RustDesk 实现了多种 NAT 穿透技术,以在不同网络环境下建立 P2P 连接:
- NAT 类型检测
- UDP 打洞
- TCP 打洞
- Relay 中转(作为后备)
## NAT 类型
### 分类
```protobuf
enum NatType {
UNKNOWN_NAT = 0; // 未知
ASYMMETRIC = 1; // 非对称 NAT (Cone NAT) - 可打洞
SYMMETRIC = 2; // 对称 NAT - 通常需要 Relay
}
```
### NAT 类型说明
| 类型 | 描述 | 可打洞 |
|------|------|--------|
| Full Cone | 外部端口固定,任何外部主机可访问 | ✅ 最容易 |
| Restricted Cone | 外部端口固定,仅允许曾发送过数据的 IP | ✅ 容易 |
| Port Restricted Cone | 外部端口固定,仅允许曾发送过数据的 IP:Port | ✅ 可能 |
| Symmetric | 每个目标地址使用不同外部端口 | ❌ 困难 |
## NAT 类型检测
### 检测原理
RustDesk 使用双端口检测法:
1. 客户端向 Rendezvous Server 的主端口 (21116) 发送 TestNatRequest
2. 同时向 NAT 测试端口 (21115) 发送 TestNatRequest
3. 比较两次响应中观测到的源端口
```
客户端 Rendezvous Server
│ │
│ TestNatRequest ────────►│ Port 21116
│ │
│ TestNatRequest ────────►│ Port 21115
│ │
│◄──────── TestNatResponse │ (包含观测到的源端口)
│ │
│ │
│ 比较两次源端口 │
│ 相同 → ASYMMETRIC │
│ 不同 → SYMMETRIC │
```
### 实现代码
**客户端发送检测请求:**
```rust
// rustdesk/src/lib.rs
pub fn test_nat_type() {
tokio::spawn(async move {
let rendezvous_server = Config::get_rendezvous_servers().first().cloned();
if let Some(host) = rendezvous_server {
// 连接主端口
let host = check_port(&host, RENDEZVOUS_PORT);
// 连接 NAT 测试端口
let host2 = crate::increase_port(&host, -1);
// 发送测试请求
let mut msg = RendezvousMessage::new();
msg.set_test_nat_request(TestNatRequest {
serial: Config::get_serial(),
});
// 收集两次响应的端口
let port1 = send_and_get_port(&host, &msg).await;
let port2 = send_and_get_port(&host2, &msg).await;
// 判断 NAT 类型
let nat_type = if port1 == port2 {
NatType::ASYMMETRIC // 可打洞
} else {
NatType::SYMMETRIC // 需要 Relay
};
Config::set_nat_type(nat_type as i32);
}
});
}
```
**服务器响应:**
```rust
// rustdesk-server/src/rendezvous_server.rs:1080-1087
Some(rendezvous_message::Union::TestNatRequest(_)) => {
let mut msg_out = RendezvousMessage::new();
msg_out.set_test_nat_response(TestNatResponse {
port: addr.port() as _, // 返回观测到的源端口
..Default::default()
});
stream.send(&msg_out).await.ok();
}
```
## UDP 打洞
### 原理
UDP 打洞利用 NAT 的端口映射机制:
```
A (内网) B (内网)
│ │
│ ──► NAT_A ──► Internet ──► NAT_B ──► (丢弃) │
│ │
│ │
│ (NAT_A 创建了映射 A:port → A_ext:port_a) │
│ │
│ │
│ (丢弃) ◄── NAT_A ◄── Internet ◄── NAT_B ◄── │
│ │
│ │
│ (NAT_B 创建了映射 B:port → B_ext:port_b) │
│ │
│ ──► NAT_A ──► Internet ──► NAT_B ──► │
│ (NAT_A 的映射存在,包被转发) │
│ │
│ ◄── NAT_A ◄── Internet ◄── NAT_B ◄── │
│ (NAT_B 的映射存在,包被转发) │
│ │
│ ◄───────── 双向通信建立 ──────────► │
```
### 实现
**被控端打洞:**
```rust
// rustdesk/src/rendezvous_mediator.rs:621-642
async fn punch_udp_hole(
&self,
peer_addr: SocketAddr,
server: ServerPtr,
msg_punch: PunchHoleSent,
) -> ResultType<()> {
let mut msg_out = Message::new();
msg_out.set_punch_hole_sent(msg_punch);
// 创建 UDP socket
let (socket, addr) = new_direct_udp_for(&self.host).await?;
let data = msg_out.write_to_bytes()?;
// 发送到 Rendezvous Server会转发给控制端
socket.send_to(&data, addr).await?;
// 多次发送以增加成功率
let socket_cloned = socket.clone();
tokio::spawn(async move {
for _ in 0..2 {
let tm = (hbb_common::time_based_rand() % 20 + 10) as f32 / 1000.;
hbb_common::sleep(tm).await;
socket.send_to(&data, addr).await.ok();
}
});
// 等待对方连接
udp_nat_listen(socket_cloned, peer_addr, peer_addr, server).await?;
Ok(())
}
```
**UDP 监听和 KCP 建立:**
```rust
// rustdesk/src/rendezvous_mediator.rs:824-851
async fn udp_nat_listen(
socket: Arc<tokio::net::UdpSocket>,
peer_addr: SocketAddr,
peer_addr_v4: SocketAddr,
server: ServerPtr,
) -> ResultType<()> {
// 连接到对方地址
socket.connect(peer_addr).await?;
// 执行 UDP 打洞
let res = crate::punch_udp(socket.clone(), true).await?;
// 建立 KCP 可靠传输层
let stream = crate::kcp_stream::KcpStream::accept(
socket,
Duration::from_millis(CONNECT_TIMEOUT as _),
res,
).await?;
// 创建连接
crate::server::create_tcp_connection(server, stream.1, peer_addr_v4, true).await?;
Ok(())
}
```
### KCP 协议
RustDesk 在 UDP 上使用 KCP 提供可靠传输KCP 特点:
- 更激进的重传策略
- 更低的延迟
- 可配置的可靠性级别
## TCP 打洞
### 原理
TCP 打洞比 UDP 困难,因为 TCP 需要三次握手。技巧是让双方同时发起连接:
```
A NAT_A NAT_B B
│ │ │ │
│ ─── SYN ───────────────►│─────────│────► (丢弃,无映射) │
│ │ │ │
│ (NAT_A 创建到 B 的映射) │ │ │
│ │ │ │
│ (丢弃,无映射) ◄─────────│─────────│◄─── SYN ───────────── │
│ │ │ │
│ │ │ (NAT_B 创建到 A 的映射) │
│ │ │ │
│ ─── SYN ───────────────►│─────────│────► SYN ───────────► │
│ │ │ (映射存在,转发成功) │
│ │ │ │
│ ◄─── SYN+ACK ──────────│─────────│◄─── SYN+ACK ───────── │
│ │ │ │
│ ─── ACK ───────────────►│─────────│────► ACK ───────────► │
│ │ │ │
│ ◄─────────── 连接建立 ─────────────────────────────────────►│
```
### 实现
```rust
// rustdesk/src/rendezvous_mediator.rs:604-617
log::debug!("Punch tcp hole to {:?}", peer_addr);
let mut socket = {
// 1. 先连接 Rendezvous Server 获取本地地址
let socket = connect_tcp(&*self.host, CONNECT_TIMEOUT).await?;
let local_addr = socket.local_addr();
// 2. 用相同的本地地址尝试连接对方
// 这会在 NAT 上创建映射
// 虽然连接会失败,但映射已建立
allow_err!(socket_client::connect_tcp_local(peer_addr, Some(local_addr), 30).await);
socket
};
// 3. 发送 PunchHoleSent 通知服务器
// 服务器会转发给控制端
let mut msg_out = Message::new();
msg_out.set_punch_hole_sent(msg_punch);
socket.send_raw(msg_out.write_to_bytes()?).await?;
// 4. 等待控制端连接
// 由于已有映射,控制端的连接可以成功
crate::accept_connection(server.clone(), socket, peer_addr, true).await;
```
## 局域网直连
### 检测同一局域网
```rust
// rustdesk-server/src/rendezvous_server.rs:721-728
let same_intranet: bool = !ws
&& (peer_is_lan && is_lan || {
match (peer_addr, addr) {
(SocketAddr::V4(a), SocketAddr::V4(b)) => a.ip() == b.ip(),
(SocketAddr::V6(a), SocketAddr::V6(b)) => a.ip() == b.ip(),
_ => false,
}
});
```
### 局域网连接流程
```
控制端 Rendezvous Server 被控端
│ │ │
│ PunchHoleRequest ────►│ │
│ │ │
│ │ (检测到同一局域网) │
│ │ │
│ │ FetchLocalAddr ──────►│
│ │ │
│ │◄────── LocalAddr ────────│
│ │ (包含被控端内网地址) │
│ │ │
│◄─ PunchHoleResponse ──│ │
│ (is_local=true) │ │
│ (socket_addr=内网地址)│ │
│ │ │
│ ─────────── 直接连接内网地址 ────────────────────►│
```
## IPv6 支持
IPv6 通常不需要 NAT 穿透,但 RustDesk 仍支持 IPv6 打洞以处理有状态防火墙:
```rust
// rustdesk/src/rendezvous_mediator.rs:808-822
async fn start_ipv6(
peer_addr_v6: SocketAddr,
peer_addr_v4: SocketAddr,
server: ServerPtr,
) -> bytes::Bytes {
crate::test_ipv6().await;
if let Some((socket, local_addr_v6)) = crate::get_ipv6_socket().await {
let server = server.clone();
tokio::spawn(async move {
allow_err!(udp_nat_listen(socket.clone(), peer_addr_v6, peer_addr_v4, server).await);
});
return local_addr_v6;
}
Default::default()
}
```
## 连接策略决策树
```
开始连接
┌───────────────┐
│ NAT 类型检测 │
└───────┬───────┘
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
ASYMMETRIC UNKNOWN SYMMETRIC
│ │ │
▼ ▼ │
┌──────────┐ ┌──────────┐ │
│ 尝试 UDP │ │ 尝试 TCP │ │
│ 打洞 │ │ 打洞 │ │
└────┬─────┘ └────┬─────┘ │
│ │ │
成功 │ 失败 成功 │ 失败 │
▼ │ ▼ │ │
┌────────┐│ ┌────────┐│ │
│UDP P2P ││ │TCP P2P ││ │
└────────┘│ └────────┘│ │
│ │ │
└───────┬───────┘ │
│ │
▼ │
┌───────────────┐ │
│ 使用 Relay │◄─────────┘
└───────────────┘
```
## 性能优化
### 多路径尝试
RustDesk 同时尝试多种连接方式,选择最快成功的:
```rust
// rustdesk/src/client.rs:342-364
let mut connect_futures = Vec::new();
// 同时尝试 UDP 和 TCP
if udp.0.is_some() {
connect_futures.push(Self::_start_inner(..., udp).boxed());
}
connect_futures.push(Self::_start_inner(..., (None, None)).boxed());
// 使用 select_ok 选择第一个成功的
match select_ok(connect_futures).await {
Ok(conn) => Ok(conn),
Err(e) => Err(e),
}
```
### 超时控制
```rust
const CONNECT_TIMEOUT: u64 = 18_000; // 18 秒
const REG_TIMEOUT: i32 = 30_000; // 30 秒
// 连接超时处理
if let Ok(Ok((stream, addr))) = timeout(CONNECT_TIMEOUT, socket.accept()).await {
// 连接成功
} else {
// 超时,尝试其他方式
}
```
## 常见问题和解决方案
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| 双 Symmetric NAT | 两端都是对称 NAT | 使用 Relay |
| 防火墙阻止 UDP | 企业防火墙 | 使用 TCP 或 WebSocket |
| 端口预测失败 | NAT 端口分配不规律 | 多次尝试或使用 Relay |
| IPv6 不通 | ISP 或防火墙问题 | 回退到 IPv4 |

View File

@@ -0,0 +1,401 @@
# RustDesk 协议 vs One-KVM 实现对比分析
本文档对比分析 RustDesk 原始协议与 One-KVM 的实现差异。
## 1. 概述
One-KVM 作为 IP-KVM 解决方案,只实现了 RustDesk 协议的**被控端Controlled** 功能不实现控制端Controller功能。这是设计决策因为 KVM 设备只需要接收远程控制,不需要控制其他设备。
### 架构差异
| 方面 | RustDesk 原版 | One-KVM |
|------|---------------|---------|
| 角色 | 双向(控制端+被控端) | 单向(仅被控端) |
| 连接方式 | P2P + Relay | 仅 Relay (TCP) |
| NAT 穿透 | UDP/TCP 打洞 + TURN | 不支持 |
| 传输协议 | UDP/TCP | 仅 TCP |
## 2. 已实现功能
### 2.1 Rendezvous 协议 (hbbs 通信)
| 消息类型 | 实现状态 | 备注 |
|----------|----------|------|
| RegisterPeer | ✅ 已实现 | 注册设备到服务器 |
| RegisterPeerResponse | ✅ 已实现 | 处理注册响应 |
| RegisterPk | ✅ 已实现 | 注册公钥 |
| RegisterPkResponse | ✅ 已实现 | 处理公钥注册响应 |
| PunchHoleSent | ✅ 已实现 | 响应打洞请求 |
| FetchLocalAddr | ✅ 已实现 | 获取本地地址 |
| LocalAddr | ✅ 已实现 | 返回本地地址 |
| RequestRelay | ✅ 已实现 | 请求中继连接 |
| RelayResponse | ✅ 已实现 | 处理中继响应 |
| ConfigUpdate | ✅ 已实现 | 接收配置更新 |
**实现文件**: `src/rustdesk/rendezvous.rs` (~829 行)
```rust
// 核心结构
pub struct RendezvousMediator {
config: RustDeskConfig,
key_pair: KeyPair,
signing_key: SigningKeyPair,
socket: UdpSocket,
status: Arc<RwLock<RendezvousStatus>>,
// ...
}
```
### 2.2 连接协议 (客户端连接)
| 消息类型 | 实现状态 | 备注 |
|----------|----------|------|
| SignedId | ✅ 已实现 | 签名身份验证 |
| PublicKey | ✅ 已实现 | 公钥交换 |
| Hash | ✅ 已实现 | 哈希挑战响应 |
| LoginRequest | ✅ 已实现 | 登录认证 |
| LoginResponse | ✅ 已实现 | 登录响应 |
| TestDelay | ✅ 已实现 | 延迟测试 |
| VideoFrame | ✅ 已实现 | 视频帧发送 |
| AudioFrame | ✅ 已实现 | 音频帧发送 |
| CursorData | ✅ 已实现 | 光标图像 |
| CursorPosition | ✅ 已实现 | 光标位置 |
| MouseEvent | ✅ 已实现 | 鼠标事件接收 |
| KeyEvent | ✅ 已实现 | 键盘事件接收 |
**实现文件**: `src/rustdesk/connection.rs` (~1349 行)
```rust
// 连接状态机
pub enum ConnectionState {
WaitingForSignedId,
WaitingForPublicKey,
WaitingForHash,
WaitingForLogin,
Authenticated,
Streaming,
}
```
### 2.3 加密模块
| 功能 | 实现状态 | 备注 |
|------|----------|------|
| Curve25519 密钥对 | ✅ 已实现 | 用于加密 |
| Ed25519 签名密钥对 | ✅ 已实现 | 用于签名 |
| Ed25519 → Curve25519 转换 | ✅ 已实现 | 密钥派生 |
| XSalsa20-Poly1305 | ✅ 已实现 | 会话加密 (secretbox) |
| 密码哈希 | ✅ 已实现 | 单重/双重 SHA256 |
| 会话密钥协商 | ✅ 已实现 | 对称密钥派生 |
**实现文件**: `src/rustdesk/crypto.rs` (~468 行)
```rust
// 密钥对结构
pub struct KeyPair {
secret_key: [u8; 32], // Curve25519 私钥
public_key: [u8; 32], // Curve25519 公钥
}
pub struct SigningKeyPair {
secret_key: [u8; 64], // Ed25519 私钥
public_key: [u8; 32], // Ed25519 公钥
}
```
### 2.4 视频/音频流
| 编码格式 | 实现状态 | 备注 |
|----------|----------|------|
| H.264 | ✅ 已实现 | 主要格式 |
| H.265/HEVC | ✅ 已实现 | 高效编码 |
| VP8 | ✅ 已实现 | WebRTC 兼容 |
| VP9 | ✅ 已实现 | 高质量 |
| AV1 | ✅ 已实现 | 新一代编码 |
| Opus 音频 | ✅ 已实现 | 低延迟音频 |
**实现文件**: `src/rustdesk/frame_adapters.rs` (~316 行)
### 2.5 HID 事件
| 功能 | 实现状态 | 备注 |
|------|----------|------|
| 鼠标移动 | ✅ 已实现 | 绝对/相对坐标 |
| 鼠标按键 | ✅ 已实现 | 左/中/右键 |
| 鼠标滚轮 | ✅ 已实现 | 垂直滚动 |
| 键盘按键 | ✅ 已实现 | 按下/释放 |
| 控制键映射 | ✅ 已实现 | ControlKey → USB HID |
| X11 键码映射 | ✅ 已实现 | X11 → USB HID |
**实现文件**: `src/rustdesk/hid_adapter.rs` (~386 行)
### 2.6 协议帧编码
| 功能 | 实现状态 | 备注 |
|------|----------|------|
| BytesCodec | ✅ 已实现 | 变长帧编码 |
| 1-4 字节头 | ✅ 已实现 | 根据长度自动选择 |
| 最大 1GB 消息 | ✅ 已实现 | 与原版一致 |
**实现文件**: `src/rustdesk/bytes_codec.rs` (~253 行)
## 3. 未实现功能
### 3.1 NAT 穿透相关
| 功能 | 原因 |
|------|------|
| UDP 打洞 | One-KVM 仅使用 TCP 中继 |
| TCP 打洞 | 同上 |
| STUN/TURN | 不需要 NAT 类型检测 |
| TestNat | 同上 |
| P2P 直连 | 设计简化,仅支持中继 |
### 3.2 客户端发起功能
| 功能 | 原因 |
|------|------|
| PunchHole (发起) | KVM 只接收连接 |
| RelayRequest | 同上 |
| ConnectPeer | 同上 |
| OnlineRequest | 不需要查询其他设备 |
### 3.3 文件传输
| 功能 | 原因 |
|------|------|
| FileTransfer | 超出 KVM 功能范围 |
| FileAction | 同上 |
| FileResponse | 同上 |
| FileTransferBlock | 同上 |
### 3.4 高级功能
| 功能 | 原因 |
|------|------|
| 剪贴板同步 | 超出 KVM 功能范围 |
| 多显示器切换 | One-KVM 使用单一视频源 |
| 虚拟显示器 | 不适用 |
| 端口转发 | 超出 KVM 功能范围 |
| 语音通话 | 不需要 |
| RDP 输入 | 不需要 |
| 插件系统 | 不支持 |
| 软件更新 | One-KVM 有自己的更新机制 |
### 3.5 权限协商
| 功能 | 原因 |
|------|------|
| Option 消息 | One-KVM 假设完全控制权限 |
| 权限请求 | 同上 |
| PermissionInfo | 同上 |
## 4. 实现差异
### 4.1 连接模式
**RustDesk 原版:**
```
客户端 ──UDP打洞──> 被控端 (P2P 优先)
└──Relay──> 被控端 (回退)
```
**One-KVM:**
```
RustDesk客户端 ──TCP中继──> hbbr服务器 ──> One-KVM设备
```
One-KVM 只支持 TCP 中继连接,不支持 P2P 直连。这简化了实现,但可能增加延迟。
### 4.2 会话加密
**RustDesk 原版:**
- 支持 ChaCha20-Poly1305 (流式)
- 支持 XSalsa20-Poly1305 (secretbox)
- 动态协商加密方式
**One-KVM:**
- 仅支持 XSalsa20-Poly1305 (secretbox)
- 使用序列号作为 nonce
```rust
// One-KVM 的加密实现
fn encrypt_message(&mut self, plaintext: &[u8]) -> Vec<u8> {
let nonce = make_nonce(&self.send_nonce);
self.send_nonce = self.send_nonce.wrapping_add(1);
secretbox::seal(plaintext, &nonce, &self.session_key)
}
```
### 4.3 视频流方向
**RustDesk 原版:**
- 双向视频流(可控制和被控制)
- 远程桌面捕获
**One-KVM:**
- 单向视频流(仅发送)
- 从 V4L2 设备捕获
- 集成到 One-KVM 的 VideoStreamManager
```rust
// One-KVM 视频流集成
pub async fn start_video_stream(&self, state: &AppState) {
let stream_manager = &state.video_stream_manager;
// 从 One-KVM 的视频管理器获取帧
}
```
### 4.4 HID 事件处理
**RustDesk 原版:**
- 转发到远程系统的输入子系统
- 使用 enigo 或 uinput
**One-KVM:**
- 转发到 USB OTG/HID 设备
- 控制物理 KVM 目标机器
```rust
// One-KVM HID 适配
pub fn convert_mouse_event(event: &RustDeskMouseEvent) -> Option<OneKvmMouseEvent> {
// 转换 RustDesk 鼠标事件到 One-KVM HID 事件
}
pub fn convert_key_event(event: &RustDeskKeyEvent) -> Option<OneKvmKeyEvent> {
// 转换 RustDesk 键盘事件到 One-KVM HID 事件
}
```
### 4.5 配置管理
**RustDesk 原版:**
- 使用 TOML/JSON 配置文件
- 硬编码默认值
**One-KVM:**
- 集成到 SQLite 配置系统
- Web UI 管理
- 使用 typeshare 生成 TypeScript 类型
```rust
#[typeshare]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RustDeskConfig {
pub enabled: bool,
pub rendezvous_server: String,
pub device_id: String,
// ...
}
```
### 4.6 设备 ID 生成
**RustDesk 原版:**
- 基于 MAC 地址和硬件信息
- 固定便携式 ID
**One-KVM:**
- 随机生成 9 位数字
- 存储在配置中
```rust
pub fn generate_device_id() -> String {
let mut rng = rand::thread_rng();
let id: u32 = rng.gen_range(100_000_000..999_999_999);
id.to_string()
}
```
## 5. 协议兼容性
### 5.1 完全兼容
| 功能 | 说明 |
|------|------|
| Rendezvous 注册 | 可与官方 hbbs 服务器通信 |
| 中继连接 | 可通过官方 hbbr 服务器中继 |
| 加密握手 | 与 RustDesk 客户端兼容 |
| 视频编码 | 支持所有主流编码格式 |
| HID 事件 | 接收标准 RustDesk 输入事件 |
### 5.2 部分兼容
| 功能 | 说明 |
|------|------|
| 密码认证 | 仅支持设备密码,不支持一次性密码 |
| 会话加密 | 仅 XSalsa20-Poly1305 |
### 5.3 不兼容
| 功能 | 说明 |
|------|------|
| P2P 连接 | 客户端必须通过中继连接 |
| 文件传输 | 不支持 |
| 剪贴板 | 不支持 |
## 6. 代码结构对比
### RustDesk 原版结构
```
rustdesk/
├── libs/hbb_common/ # 公共库
│ ├── protos/ # Protobuf 定义
│ └── src/
├── src/
│ ├── server/ # 被控端服务
│ ├── client/ # 控制端
│ ├── ui/ # 用户界面
│ └── rendezvous_mediator.rs
```
### One-KVM 结构
```
src/rustdesk/
├── mod.rs # 模块导出
├── config.rs # 配置类型 (~164 行)
├── crypto.rs # 加密模块 (~468 行)
├── bytes_codec.rs # 帧编码 (~253 行)
├── protocol.rs # 消息辅助 (~170 行)
├── rendezvous.rs # Rendezvous 中介 (~829 行)
├── connection.rs # 连接处理 (~1349 行)
├── hid_adapter.rs # HID 转换 (~386 行)
└── frame_adapters.rs # 视频/音频适配 (~316 行)
```
**总计**: ~3935 行代码
## 7. 总结
### 实现率统计
| 类别 | RustDesk 功能数 | One-KVM 实现数 | 实现率 |
|------|-----------------|----------------|--------|
| Rendezvous 协议 | 15+ | 10 | ~67% |
| 连接协议 | 30+ | 12 | ~40% |
| 加密功能 | 8 | 6 | 75% |
| 视频/音频 | 6 | 6 | 100% |
| HID 功能 | 6 | 6 | 100% |
### 设计理念
One-KVM 的 RustDesk 实现专注于 **IP-KVM 核心功能**:
1. **精简**: 只实现必要的被控端功能
2. **可靠**: 使用 TCP 中继保证连接稳定性
3. **集成**: 与 One-KVM 现有视频/HID 系统无缝集成
4. **安全**: 完整实现加密和认证机制
### 客户端兼容性
One-KVM 可与标准 RustDesk 客户端配合使用:
- RustDesk 桌面客户端 (Windows/macOS/Linux)
- RustDesk 移动客户端 (Android/iOS)
- RustDesk Web 客户端
只需确保:
1. 配置相同的 Rendezvous 服务器
2. 使用设备 ID 和密码连接
3. 客户端支持中继连接