869 lines
37 KiB
Markdown
869 lines
37 KiB
Markdown
|
||
# 关于此分支
|
||
|
||
> **原始仓库**:https://github.com/lejianwen/rustdesk-server.git
|
||
> **克隆时间**:2026-09-16
|
||
> **镜像仓库**:https://gitea7.yangjieshao.work/rustdesk/rustdesk-server.git
|
||
> **子模块 hbb_common**:https://gitea7.yangjieshao.work/rustdesk/hbb_common.git
|
||
|
||
|
||
|
||
|
||
---
|
||
|
||
# 本仓库修改
|
||
|
||
## 编译与打包步骤
|
||
|
||
### 1. 编译服务器二进制(hbbs / hbbr / rustdesk-utils)
|
||
|
||
使用持久化 Rust 容器编译:
|
||
|
||
```sh
|
||
# 启动编译容器
|
||
docker run -d --name rustbuild --network host \
|
||
-v /root/rustdesk/lejianwen/rustdesk-server:/srv -w /srv rust:alpine sleep infinity
|
||
|
||
# 容器内:换阿里云 apk 源
|
||
printf 'https://mirrors.aliyun.com/alpine/v3.24/main\nhttps://mirrors.aliyun.com/alpine/v3.24/community\n' > /etc/apk/repositories
|
||
|
||
# 安装依赖
|
||
apk add build-base pkgconf openssl-dev openssl-libs-static git
|
||
|
||
# 配置 cargo(rsproxy 源 + CLI git fetch)
|
||
# /usr/local/cargo/config.toml:
|
||
# [source.crates-io] replace-with = "rsproxy-sparse"
|
||
# [source.rsproxy-sparse] registry = "sparse+https://rsproxy.cn/index/"
|
||
# [net] git-fetch-with-cli = true
|
||
|
||
# GitHub 加速(容器内系统级改写)
|
||
git config --system url.'https://gh-proxy.com/https://github.com/'.insteadOf 'https://github.com/'
|
||
|
||
# 编译
|
||
docker exec rustbuild cargo build --release
|
||
```
|
||
|
||
编译产物:`target/release/hbbs`、`target/release/hbbr`、`target/release/rustdesk-utils`
|
||
|
||
### 2. 构建 s6 基础镜像
|
||
|
||
```sh
|
||
# 拷贝二进制到 rootfs
|
||
docker cp rustbuild:/srv/target/release/hbbs /root/rustdesk/build/s6img/rootfs/usr/bin/
|
||
docker cp rustbuild:/srv/target/release/hbbr /root/rustdesk/build/s6img/rootfs/usr/bin/
|
||
docker cp rustbuild:/srv/target/release/rustdesk-utils /root/rustdesk/build/s6img/rootfs/usr/bin/
|
||
|
||
# 构建镜像(必须 --no-cache,否则 COPY rootfs 走缓存)
|
||
docker build --no-cache \
|
||
-f /root/rustdesk/build/s6img/Dockerfile.local \
|
||
-t rustdesk-server-s6:1.1.17-forapi \
|
||
/root/rustdesk/build/s6img
|
||
```
|
||
|
||
### 3. 编译 API 二进制(apimain)
|
||
|
||
```sh
|
||
# 启动 Go 编译容器
|
||
docker run -d --name gobuild --network host \
|
||
-v /root/rustdesk/build/apibuild:/srv -w /srv golang:1.23-alpine sleep infinity
|
||
|
||
# 安装 git + gcc(CGO 静态编译需要)
|
||
docker exec gobuild apk add --no-cache git gcc musl-dev
|
||
|
||
# 设置 Go 代理(直连 GitHub 或国内镜像)
|
||
docker exec gobuild go env -w GOPROXY=https://goproxy.cn,direct
|
||
|
||
# 编译(CGO 静态编译)
|
||
docker exec gobuild env CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=gcc \
|
||
go build -ldflags '-s -w' -o ./amd64/release/apimain ./cmd/apimain.go
|
||
```
|
||
|
||
### 4. 构建 admin 前端
|
||
|
||
```sh
|
||
# admin 前端来自 lejianwen/rustdesk-api-web
|
||
git clone --depth 1 -b master https://gh-proxy.com/https://github.com/lejianwen/rustdesk-api-web
|
||
docker run --rm -v $PWD:/srv -w /srv node:20-alpine sh -c \
|
||
"npm config set registry https://registry.npmmirror.com && npm install && npm run build"
|
||
cp -a rustdesk-api-web/dist/. /root/rustdesk/build/apibuild/resources/admin/
|
||
```
|
||
|
||
### 5. 打完整镜像
|
||
|
||
```sh
|
||
# 修改 Dockerfile_full_s6 首行,指向本地 s6 基础镜像
|
||
cp /root/rustdesk/build/apibuild/Dockerfile_full_s6 /tmp/Dockerfile_full_s6
|
||
sed -i 's|FROM rustdesk/rustdesk-server-s6:latest AS server|FROM rustdesk-server-s6:1.1.17-forapi AS server|' /tmp/Dockerfile_full_s6
|
||
|
||
# 构建(context 必须含 amd64/release)
|
||
docker build -f /tmp/Dockerfile_full_s6 \
|
||
--build-arg BUILDARCH=amd64 \
|
||
-t yangjieshao/rustdesk-api:full-s6-amd64 \
|
||
/root/rustdesk/build/apibuild
|
||
```
|
||
|
||
### 6. 验证运行
|
||
|
||
```sh
|
||
docker run -d --name rustdesk-test -p 21114-21119:21114-21119 \
|
||
-p 21116:21116/udp \
|
||
-e RELAY=<IP[:21117]> \
|
||
-v <data>:/data -v <appdata>:/app/data \
|
||
yangjieshao/rustdesk-api:full-s6-amd64
|
||
|
||
curl -s http://127.0.0.1:21114/_admin/ -o /dev/null -w '%{http_code}\n'
|
||
```
|
||
|
||
### 构建产物
|
||
|
||
| 镜像 | 说明 |
|
||
|---|---|
|
||
| `rustdesk-server-s6:1.1.17-forapi` | s6 基础镜像(hbbs + hbbr + rustdesk-utils) |
|
||
| `yangjieshao/rustdesk-api:full-s6-amd64` | 完整镜像(+ apimain + admin 前端,s6 托管) |
|
||
|
||
---
|
||
|
||
### 中继限速配置
|
||
|
||
hbbr(中继服务器)支持三个限速参数,均通过环境变量设置,**单位 Mb/s**:
|
||
|
||
| 参数 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `LIMIT_SPEED` | **32** | 单连接限速 |
|
||
| `TOTAL_BANDWIDTH` | **1024** | 中继总带宽上限 |
|
||
| `SINGLE_BANDWIDTH` | **128** | 单连接带宽上限 |
|
||
|
||
**启动时配置(Docker 环境变量):**
|
||
|
||
```bash
|
||
docker run ... \
|
||
-e LIMIT_SPEED=100 \
|
||
-e TOTAL_BANDWIDTH=2000 \
|
||
-e SINGLE_BANDWIDTH=500 \
|
||
yangjieshao/rustdesk-api:full-s6-amd64
|
||
```
|
||
|
||
**运行时修改(连接控制台):**
|
||
|
||
| 命令 | 说明 |
|
||
|---|---|
|
||
| `limit-speed <值>` | 设置单连接限速 |
|
||
| `total-bandwidth <值>` | 设置总带宽 |
|
||
| `single-bandwidth <值>` | 设置单连接带宽 |
|
||
|
||
> ⚠️ **设为无限制**:代码中 `if v > 0.` 判断导致设 `0` 会被忽略。如需无限制,请设一个极大值,例如 `-e LIMIT_SPEED=100000`(≈100 Gb/s,实际等同无限制)。
|
||
|
||
---
|
||
|
||
# 1.1.17 合并记录
|
||
|
||
> 日期:2026-09-05
|
||
> 关联文档:[lejianwen/rustdesk-api README](https://github.com/lejianwen/rustdesk-api)
|
||
> 说明:将官方 `rustdesk/rustdesk-server` 1.1.17 合并到本仓库(forapi 分支),完整构建记录见 [lejianwen/rustdesk-api README](https://github.com/lejianwen/rustdesk-api)。
|
||
|
||
## 合并要点
|
||
|
||
- 官方 1.1.14(merge-base `c650217`)→ 1.1.17,共 43 个上游提交
|
||
- **安全**:禁用 UDP 打洞反射/放大路径(`PunchHoleRequest`/`PunchHoleSent`/`LocalAddr` 只允许走 TCP/WS)
|
||
- **功能**:`-b/--bind` 绑定指定 IP;控制台监听独立绑到 127.0.0.1;`punch-requests(pr)` 连接日志控制台命令
|
||
- **修复**:在线状态 `i32` 溢出(运行 24.9~49.7 天后误报在线);中继限速默认值 4Mb/s → 32Mb/s
|
||
- **依赖**:`hbb_common` 子模块推进到 `69cea8d`、新增 `flate2`
|
||
|
||
## 冲突处理
|
||
|
||
| 冲突文件 | 处理方式 |
|
||
|---|---|
|
||
| `src/rendezvous_server.rs` | 保留 fork 的 JWT/必须登录/KeyExchange/加密 Sink/API 特性,叠加上游全部修复,共 4 处冲突块手动合并 |
|
||
| `src/main.rs` | 双方参数合并:上游的 `--bind` + DEPRECATED 标注,保留 fork 的 `--must-login` |
|
||
| `libs/hbb_common`(子模块) | 从 fork 的旧提交直接指到官方 1.1.17 用的 `69cea8d` |
|
||
| `Cargo.toml` / `Cargo.lock` | git 自动合并成功 |
|
||
|
||
**注意点**:
|
||
- fork 侧 `check_cmd` 帮助文本比上游多一行 `must-login`,合并 `punch-requests` 时要同步把 format! 占位符从 7 个加到 8 个,**否则编译报 `argument never used`**
|
||
- 上游的 `peers_online_state` 是内联代码,fork 抽成了 helper(`handle_online_request` 复用),合并时保留 helper 并把 `as i32` 改 `as i64`
|
||
|
||
---
|
||
|
||
# 自连拦截(A 远程连 A)服务器端方案
|
||
|
||
> 状态:**已实施并通过端到端验证**(2026-09-06)
|
||
> 日期:2026-09-05 设计 / 2026-09-06 实施
|
||
> 关联文档:[lejianwen/rustdesk-api README](https://github.com/lejianwen/rustdesk-api)
|
||
>
|
||
> v2 变更:经用户提示,发现 API 的 `UserToken` 表**已存在 token→设备ID 映射**,
|
||
> 方案从"JWT 加 claim"改为"映射查询",JWT 结构完全不动。
|
||
> 实施时额外修复:API 签发的 JWT 增加 `jti`(见 4.4)。
|
||
|
||
## 1. 问题描述
|
||
|
||
用户偶尔会误操作"用 A 电脑远程连接 A 电脑自己"(比如通讯录里点了自己、输错 ID),连接一旦成功,画面里包含自己的画面,形成无限嵌套(镜中镜)。
|
||
|
||
- 该问题正统修法在**客户端**(发起前检查目标 == 自己),但 rustdesk 客户端仓库太大、用户不希望动客户端
|
||
- 因此考虑**服务器端**拦截:发现"自己连自己"时直接拒绝,并把原因返回给客户端弹窗
|
||
|
||
## 2. 服务器端能拦截的位置分析
|
||
|
||
连接建立的时序(hbbs = ID/信令服务器,hbbr = 中继):
|
||
|
||
```
|
||
主控端 A ──PunchHoleRequest──▶ hbbs ──PunchHoleResponse──▶ 主控端 A
|
||
│
|
||
└──PunchHoleSent──▶ 被控端 B
|
||
(之后 A/B 直连打洞,或双方连 hbbr 中继)
|
||
```
|
||
|
||
**关键结论:拦截窗口只有 hbbs 处理 `PunchHoleRequest` / `RequestRelay` 这两步。**
|
||
一旦放行、两端直连建立(本机自连几乎必然直连成功),后续流量完全不经服务器,无法感知也无法切断。
|
||
|
||
API(Go 服务)不在打洞数据路径上(客户端直连 hbbs 的 21116/21118),所以 API 层没有拦截点;hbbr 也只在打洞失败后才登场。**唯一可行的服务端拦截点就是 hbbs。**
|
||
|
||
## 3. 代码调查结论(证据)
|
||
|
||
### 3.1 协议里没有"发起方设备 ID"
|
||
|
||
`libs/hbb_common/protos/rendezvous.proto`:
|
||
|
||
```proto
|
||
message PunchHoleRequest {
|
||
string id = 1; // 目标设备 ID(有)
|
||
NatType nat_type = 2;
|
||
string licence_key = 3;
|
||
ConnType conn_type = 4;
|
||
string token = 5; // ← 发起方的登录 token(fork 已在用)
|
||
...
|
||
} // 没有任何"发起方自己 ID"字段
|
||
|
||
message RequestRelay {
|
||
string id = 1; // 目标设备 ID
|
||
...
|
||
string token = 8; // ← 中继请求也带 token
|
||
...
|
||
}
|
||
```
|
||
|
||
→ "目标 ID == 发起 ID 就拒"的规则本身正确,但服务器必须先知道**发起方是谁**。
|
||
|
||
### 3.2 token(JWT)里目前只有账号,但 API 数据库里已有 token→设备ID 映射
|
||
|
||
- hbbs 侧(`src/jwt.rs`):`Claims { user_id, exp }` —— JWT 本身不含设备
|
||
- API 签发侧(`rustdesk-api/service/user.go:89`):`Jwt.GenerateToken(u.Id)` 只放 user_id
|
||
- 仅凭 user_id 对比目标设备归属 → 会误伤"同账号下 A 连自己的另一台设备 B"(合法主场景)❌
|
||
- **但**:API 的 `model/userToken.go` `UserToken` 表已存 `Token + DeviceId + DeviceUuid + ExpiredAt`,
|
||
且 `Login()`(`service/user.go:97`)签发时就写入 `DeviceId: llog.DeviceId`,`Logout` 时删行。
|
||
即 **token → 设备ID 的映射已经持久化在 API 的 sqlite 中**(v2 方案的基础)。
|
||
|
||
### 3.3 映射数据的来源:登录时 API 已经拿到发起方设备 ID
|
||
|
||
`rustdesk-api/http/request/api/user.go:31`:
|
||
|
||
```go
|
||
type LoginForm struct {
|
||
AutoLogin bool `json:"autoLogin"`
|
||
DeviceInfo DeviceInfoInLogin `json:"deviceInfo"`
|
||
Id string `json:"id"` // ← 客户端自己的 rustdesk ID
|
||
Type string `json:"type"`
|
||
Uuid string `json:"uuid"`
|
||
Username string `json:"username"`
|
||
Password string `json:"password,omitempty"`
|
||
}
|
||
```
|
||
|
||
桌面客户端和 web 客户端登录 API 都走这个接口并携带 `Id`,登录流程将其写入 `LoginLog.DeviceId` 并落到 `UserToken.DeviceId` —— **无需客户端任何改动,设备身份已经入库**。
|
||
|
||
### 3.4 MUST_LOGIN 只影响发起方(已确认)
|
||
|
||
hbbs 中 MUST_LOGIN 的强制点**只有一处**:`src/rendezvous_server.rs:932`(`handle_punch_hole_request` 内),即只有发起打洞的会话要求 token。
|
||
|
||
| 消息 | 发送方 | MUST_LOGIN=Y 时 |
|
||
|---|---|---|
|
||
| `PunchHoleRequest` | 发起方 | 必须带有效 token |
|
||
| `RequestRelay` | 发起方(force_relay 场景) | **目前不检查(现状缺口)** |
|
||
| `RegisterPeer`(注册/心跳) | 被控端 | 不检查,不登录可被连 |
|
||
| `RegisterPk` | 被控端 | 不检查 |
|
||
| hbbr 中继连接 | 双方 | 无校验 |
|
||
|
||
→ 被连接设备**不需要**登录;MUST_LOGIN=Y 恰好只保证"发起方必带 token",与自连拦截(只需要发起方身份)完全匹配。
|
||
|
||
### 3.5 错误原因回传客户端有现成通道
|
||
|
||
现有代码已经用 `PunchHoleResponse.other_failure` 给客户端弹窗:
|
||
|
||
```rust
|
||
msg_out.set_punch_hole_response(PunchHoleResponse {
|
||
other_failure: String::from("Connection failed, please login!"),
|
||
..Default::default()
|
||
});
|
||
```
|
||
|
||
→ 自连拒绝提示用同一字段,客户端零改动。
|
||
|
||
## 4. 推荐方案(v2:映射查询,不改 JWT)
|
||
|
||
数据流:
|
||
|
||
```
|
||
客户端登录 ──LoginForm{Id}──▶ API:签发JWT + UserToken{Token, DeviceId}(已有)
|
||
主控端发起连接 ──PunchHoleRequest{id, token}──▶ hbbs
|
||
hbbs:验JWT签名/有效期(逻辑不变)
|
||
→ 按 token 查 UserToken 得 device_id(内存缓存 → 未命中走 API 内部接口)
|
||
→ device_id == ph.id ?
|
||
是 → 回 PunchHoleResponse{other_failure:"不能远程自己"},拒绝
|
||
否 → 正常打洞
|
||
```
|
||
|
||
### 改动 1:API(rustdesk-api,Go,改动极小)
|
||
|
||
- 确认登录时 `LoginLog.DeviceId` 取自 `LoginForm.Id`(rustdesk ID),对不上则补一行赋值
|
||
- 新增内部查询接口(如 `GET /api/internal/token-device`,入参 token,返回 device_id),
|
||
**仅允许 localhost/容器内访问**(IP 判断 + 共享密钥头),不记日志避免 token 泄露
|
||
|
||
### 改动 2:hbbs(lejianwen/rustdesk-server,Rust,约 40 行)
|
||
|
||
- `handle_punch_hole_request`:验签通过后查映射(`token 哈希 → device_id`,
|
||
hbbs 内置 HashMap 缓存 + TTL 几分钟;未命中调 API 内部接口),
|
||
`device_id == ph.id` → 拒绝并回 `other_failure`
|
||
- `RequestRelay` 分支(`handle_tcp`):**补上 token 校验**(顺带堵掉 force_relay 绕过 MUST_LOGIN 的口子)+ 同样的自连比对
|
||
- **fail-open**:API 不可达/查询失败 → 视为"无设备信息",跳过自连检查(不拖累正常连接)
|
||
|
||
### 兜底规则(已确认的设计决策):无 token 一律跳过校验
|
||
|
||
自连校验的触发条件是"请求携带有效 token 且查得到 device_id",**与 MUST_LOGIN 取值解耦**:
|
||
|
||
| 场景 | 行为 |
|
||
|---|---|
|
||
| MUST_LOGIN=Y,任意发起方 | 必有 token → 校验生效,100% 拦截 |
|
||
| MUST_LOGIN=N,发起方已登录 | 带 token → 顺路校验,同样拦截(免费保护) |
|
||
| MUST_LOGIN=N,发起方未登录 | 无 token → **跳过校验直接放行**,保持现状行为(套娃风险被明确接受) |
|
||
|
||
理由:没有 token 就没有设备身份,无从判断"是不是自己连自己";强行用 IP/user_id 猜
|
||
只会引入误伤(见第 5 节)。跳过 = 该场景退化为现状,不比现在更差,已是改动范围内的最优解。
|
||
|
||
### 改动 3:开启 `MUST_LOGIN=Y`
|
||
|
||
- 保证所有发起连接的会话都带 token → 拦截覆盖率 100%
|
||
- 代价即其既有语义:所有"主动发起连接"的设备必须登录;纯被连设备不受影响
|
||
|
||
### 明确不推荐:hbbs 直读 API 的 sqlite
|
||
|
||
同容器共享 `/app/data` 技术上可行,但 Go gorm 与 Rust sqlx 双进程写同一 sqlite 有锁风险,
|
||
且 hbbs 从此耦合 API 表结构(API 升级改表即崩)——映射读取统一走 HTTP 内部接口。
|
||
|
||
### 4.4 实施补充:JWT 签发增加 jti
|
||
|
||
实施中发现:JWT claims 只有 `{user_id, exp}`,同一账号**同一秒内**多次登录会产出
|
||
字节级相同的 token,UserToken 表中无法区分设备(hbbs 查映射会命中错误设备)。
|
||
修复:`lib/jwt/jwt.go` GenerateToken 的 RegisteredClaims 增加 `ID: uuid.NewString()`(jti),
|
||
每次登录 token 唯一。旧客户端/旧 token 不受影响(Rust 侧 serde 忽略未知字段)。
|
||
|
||
## 4.5 实际改动清单(已全部落地)
|
||
|
||
**rustdesk-api(Go)**
|
||
|
||
| 文件 | 改动 |
|
||
|---|---|
|
||
| `service/user.go` | 新增 `GetDeviceIdByToken(token)`:查 UserToken(校验 ExpiredAt)返回 DeviceId |
|
||
| `http/controller/api/internal.go` | 新增内部接口 `GET /api/internal/token-device?token=`:仅回环 IP + `X-Secret` 头必须等于 JWT 密钥,否则 403;查不到 404 |
|
||
| `http/router/api.go` | 注册路由(置于 RustAuth 中间件之前) |
|
||
| `lib/jwt/jwt.go` | GenerateToken 增加 jti |
|
||
|
||
**lejianwen/rustdesk-server(Rust)**
|
||
|
||
| 文件 | 改动 |
|
||
|---|---|
|
||
| `src/token_device.rs`(新增) | 映射查询模块:`get_device_id(token)`,内存缓存(命中 TTL 300s / 未命中 60s,容量上限 4096),minreq 在 `spawn_blocking` 中调 API,超时 3s,任何失败返回 None(fail-open) |
|
||
| `src/lib.rs` | 注册 `mod token_device` |
|
||
| `src/rendezvous_server.rs` `handle_punch_hole_request` | MUST_LOGIN 验签后:token 有效且 `get_device_id == ph.id` → 返回 `PunchHoleResponse{other_failure:"不能远程自己…/You are connecting to yourself"}`,不打洞 |
|
||
| `src/rendezvous_server.rs` `handle_tcp` RequestRelay 分支 | **补上缺失的 MUST_LOGIN token 校验**(堵 force_relay 绕过口子)+ 同样的自连比对,命中则丢弃中继请求并记 warn 日志 |
|
||
| `src/main.rs` | `--must-login` 帮助文本标注 default Y |
|
||
| `src/rendezvous_server.rs` `start_with_bind` | **MUST_LOGIN 默认值改为 Y**:仅当显式 `--must-login=N` 或 `MUST_LOGIN=N` 时关闭 |
|
||
| `docker-compose.yml` | 示例 env `MUST_LOGIN=N` → `Y` |
|
||
|
||
**运行配置**:hbbs 与 API 同容器,`RUSTDESK_API_JWT_KEY` 双方共享(hbbs 的 jwt::SECRET 即 API 的 jwt.key);
|
||
API 内部地址可用 `RUSTDESK_API_INTERNAL_URL` 覆盖,默认 `http://127.0.0.1:21114`。
|
||
|
||
> ⚠️ **部署必设 `RUSTDESK_API_JWT_KEY`**(任意字符串)。未设置时 hbbs 的 JWT 密钥为空,
|
||
> token 校验和自连拦截**整体静默关闭**(fail-open 设计,日志无报错,连接照常放行)。
|
||
> 新版本已在启动日志中显式告警:`RUSTDESK_API_JWT_KEY is not set: ... DISABLED`;
|
||
> 设置后日志显示 `Self-connection blocking: enabled`。修改该值后客户端需退出重新登录。
|
||
|
||
## 4.6 端到端验证结果(2026-09-06,全部通过)
|
||
|
||
验证方法:Python 脚本按 hbb_common `bytes_codec` 帧格式与 rendezvous.proto 手工构造
|
||
`PunchHoleRequest` 直连 hbbs 21116 端口(脚本:`/root/rustdesk/build/punch_test.py`)。
|
||
|
||
| 用例 | 结果 |
|
||
|---|---|
|
||
| 自连:token 属于设备 11010050,目标 11010050 | ✅ 拦截,客户端收到 `other_failure: "不能远程自己:发起方与目标是同一台设备 / You are connecting to yourself"`,hbbs 记 `Blocked self-connection attempt` warn 日志 |
|
||
| 误伤检查:token 属于设备 99999999,目标 11010050 | ✅ 放行(映射返回 99999999 ≠ 目标),无误拦 |
|
||
| 无 token(MUST_LOGIN=Y) | ✅ 拒绝:`Connection failed, please login!` |
|
||
| 内部接口:正确密钥 + 回环 | ✅ 返回 `{"device_id":"11010050"}` |
|
||
| 内部接口:错误密钥 / 非回环来源 | ✅ 均 403 |
|
||
| MUST_LOGIN 默认值 | ✅ 未配置任何 env 时日志显示 `MUST_LOGIN=Y` |
|
||
|
||
注:验证容器中以 `-k ""` 跳过密钥交换直连明文路径(打洞处理代码与加密路径完全相同),
|
||
正式镜像仍按官方流程自动生成密钥对并对 TCP 连接先发 KeyExchange 阶段 1,由客户端正常完成握手。
|
||
|
||
## 5. 为什么其他做法不行 / 不选
|
||
|
||
| 做法 | 结论 |
|
||
|---|---|
|
||
| 拿 user_id 对比目标设备归属 | 误伤同账号多设备互连(RustDesk 的主场景)❌ |
|
||
| 拿来源 IP 对比目标注册 IP | 误伤整个局域网 A→B(同一 NAT 出口)❌ |
|
||
| API/前端层拦截 | API 不在打洞路径上,没有拦截点 ❌ |
|
||
| 连接建立后检测/切断 | 直连流量不经服务器,无法感知 ❌ |
|
||
| hbbs 直读 API 的 sqlite | 双进程双 ORM 同库有锁风险,hbbs 耦合 API 表结构 ❌(见 4 节末) |
|
||
| rustdesk-api-web 前端加"目标==自己"提示 | 可选退路:只覆盖 webclient 点击路径,桌面客户端管不到;本质仍是客户端补丁 ⚠️ |
|
||
| 客户端修复(正统) | 用户排除(客户端仓库太大)⛔ |
|
||
| 协议增强:`PunchHoleRequest` 加 `my_id` 字段 | 长期正解,proto3 加字段向后兼容,但需要改客户端;**远期项**,服务端将来可"有字段就校验、无字段跳过" |
|
||
|
||
**JWT 加 claim(v1 方案)降为备选**:同样可行且无运行时查询,但改动面(改签发+改 Claims 结构)
|
||
比映射查询大,且登出不能即时失效设备身份;若将来想去掉对 API 的运行时依赖可切换。
|
||
|
||
| 对比项 | v2 映射查询(推荐) | v1 JWT 加 claim(备选) |
|
||
|---|---|---|
|
||
| JWT 结构 | 不动 | 加字段(向后兼容) |
|
||
| API 改动 | 加 1 个内部接口(映射已存在) | 改签发逻辑 |
|
||
| hbbs 改动 | 两处校验 + 查询缓存 | 两处校验 |
|
||
| 运行时依赖 | 查询时 API 参与一次(有缓存,fail-open) | 无 |
|
||
| 登出语义 | 设备身份即时失效(增强) | token 到期前一直有效 |
|
||
|
||
## 6. 覆盖范围与边缘情况
|
||
|
||
- **MUST_LOGIN=Y**:webclient 与桌面客户端发起 → 100% 拦截
|
||
(`LoginForm.Id` → `UserToken.DeviceId` 提供设备身份)
|
||
- **MUST_LOGIN=N**:已登录设备发起 → 拦截;未登录设备发起 → 放行(见第 4 节兜底规则,
|
||
套娃风险被明确接受,行为与现状一致)
|
||
- **MUST_LOGIN=Y 时未登录客户端**(无 token)→ 本来就被拒绝("please login"),不存在漏网
|
||
- API 暂时不可达 → fail-open,自连拦截暂时降级,正常连接不受影响
|
||
- **边缘情况**:客户端 ID 变更(重装/清数据)到重新登录之间的短窗口内,映射里的旧 device_id
|
||
与目标不匹配 → 可能漏拦或误拦一次,重新登录即恢复,影响很小
|
||
- 实施时需抽查验证 `UserToken.DeviceId` 对各端(桌面/webclient/OAuth 登录)都已正确填充
|
||
- 错误文案建议中英对照,走 `other_failure`
|
||
|
||
## 7. 名词澄清:hbbs 的代码就在本机
|
||
|
||
"hbbs"不是另一个项目,是 `lejianwen/rustdesk-server` 仓库编译出的二进制名(`Cargo.toml` 里 `[[bin]] name = "hbbs"`),主体代码即 `src/rendezvous_server.rs`。本机已有该仓库(刚完成 1.1.17 合并)、依赖子模块 hbb_common、完整容器编译链路——改完重新打镜像约十几分钟,无需再拉取任何大仓库(不动的 rustdesk 客户端仓库才是大家伙)。
|
||
|
||
## 8. 实施决策记录
|
||
|
||
1. ✅ 按 v2 方案实施(API 内部查询接口 + hbbs 两处校验,JWT 结构不动,仅加 jti)
|
||
2. ✅ 错误文案:`不能远程自己:发起方与目标是同一台设备 / You are connecting to yourself`
|
||
3. ✅ 已顺带补上 `RequestRelay` 缺失的 token 校验(堵住 MUST_LOGIN 的绕过口子)
|
||
4. ✅ 查不到映射(API 不可达)时 fail-open:只影响自连拦截,不拖累正常连接
|
||
5. ✅ MUST_LOGIN 默认值改为 Y(显式配置 N 可关闭)
|
||
6. ⏳ 远期项(未实施):`PunchHoleRequest` 增加 `my_id` 字段(需改客户端,proto3 向后兼容)
|
||
---
|
||
|
||
# 原始 README 内容
|
||
|
||
> 以下为 `lejianwen/rustdesk-server` 原始仓库的 README,本仓库在此基础上增加了
|
||
> 自连拦截(A 远程连 A)服务器端方案和 1.1.17 合并支持。
|
||
|
||
[](https://github.com/lejianwen/rustdesk-server/actions/workflows/build.yaml)
|
||
|
||
- 解决当客户端登录了`API`账号时链接超时的问题
|
||
- s6镜像添加了`API`支持,`API`开源地址 https://github.com/lejianwen/rustdesk-api
|
||
- 是否必须登录才能链接, `MUST_LOGIN` 默认为 `N`,设置为 `Y` 则必须登录才能链接
|
||
- `RUSTDESK_API_JWT_KEY`,设置后会通过`JWT`校验token的合法性
|
||
- 支持client websocket (client >= 1.4.1)
|
||
|
||
## docker镜像地址
|
||
|
||
- s6 镜像 [lejianwen/rustdesk-server-s6](https://hub.docker.com/r/lejianwen/rustdesk-server-s6)
|
||
|
||
```yaml
|
||
networks:
|
||
rustdesk-net:
|
||
external: false
|
||
services:
|
||
rustdesk:
|
||
ports:
|
||
- 21114:21114
|
||
- 21115:21115
|
||
- 21116:21116
|
||
- 21116:21116/udp
|
||
- 21117:21117
|
||
- 21118:21118
|
||
- 21119:21119
|
||
image: lejianwen/rustdesk-server-s6:latest
|
||
environment:
|
||
- RELAY=<relay_server[:port]>
|
||
- ENCRYPTED_ONLY=1
|
||
- MUST_LOGIN=N
|
||
- TZ=Asia/Shanghai
|
||
- RUSTDESK_API_RUSTDESK_ID_SERVER=<id_server[:21116]>
|
||
- RUSTDESK_API_RUSTDESK_RELAY_SERVER=<relay_server[:21117]>
|
||
- RUSTDESK_API_RUSTDESK_API_SERVER=http://<api_server[:21114]>
|
||
- RUSTDESK_API_KEY_FILE=/data/id_ed25519.pub
|
||
- RUSTDESK_API_JWT_KEY=xxxxxx # jwt key
|
||
volumes:
|
||
- /data/rustdesk/server:/data
|
||
- /data/rustdesk/api:/app/data #将数据库挂载
|
||
networks:
|
||
- rustdesk-net
|
||
restart: unless-stopped
|
||
|
||
```
|
||
|
||
- 普通镜像 [lejianwen/rustdesk-server](https://hub.docker.com/r/lejianwen/rustdesk-server)
|
||
|
||
|
||
# API功能截图
|
||
|
||

|
||
|
||

|
||
|
||
更多查看 [RustDesk Api](https://github.com/lejianwen/rustdesk-api)
|
||
|
||
|
||
---
|
||
|
||
<p align="center">
|
||
<a href="#如何自行构建">自行构建</a> •
|
||
<a href="#Docker-镜像">Docker</a> •
|
||
<a href="#基于-S6-overlay-的镜像">S6-overlay</a> •
|
||
<a href="#如何创建密钥">密钥</a> •
|
||
<a href="#deb-套件">Debian</a> •
|
||
<a href="#ENV-环境参数">环境参数</a><br>
|
||
[<a href="README-EN.md">English</a>] | [<a href="README-DE.md">Deutsch</a>] | [<a href="README-NL.md">Nederlands</a>] | [<a href="README-TW.md">繁体中文</a>]<br>
|
||
</p>
|
||
|
||
# RustDesk Server Program
|
||
|
||
|
||
|
||
[**下载**](https://github.com/lejianwen/rustdesk-server/releases)
|
||
|
||
[**说明文件**](https://rustdesk.com/docs/zh-cn/self-host/)
|
||
|
||
自行搭建属于你的RustDesk服务器,所有的一切都是免费且开源的
|
||
|
||
## 如何自行构建
|
||
|
||
```bash
|
||
cargo build --release
|
||
```
|
||
|
||
执行后会在target/release目录下生成三个对应平台的可执行程序
|
||
|
||
- hbbs - RustDesk ID/会和服务器
|
||
- hbbr - RustDesk 中继服务器
|
||
- rustdesk-utils - RustDesk 命令行工具
|
||
|
||
您可以在 [releases](https://github.com/lejianwen/rustdesk-server/releases) 页面中找到最新的服务端软件。
|
||
|
||
如果您需要额外的功能支持,[RustDesk 专业版服务器](https://rustdesk.com/pricing.html) 获取更适合您。
|
||
|
||
如果您想开发自己的服务器,[rustdesk-server-demo](https://github.com/rustdesk/rustdesk-server-demo) 应该会比直接使用这个仓库更简单快捷。
|
||
|
||
## Docker 镜像
|
||
|
||
Docker镜像会在每次 GitHub 发布新的release版本时自动构建。我们提供两种类型的镜像。
|
||
|
||
### Classic 传统镜像
|
||
|
||
这个类型的镜像是基于 `ubuntu-20.04` 进行构建,镜像仅包含两个主要的可执行程序(`hbbr` 和 `hbbs`)。它们可以通过以下tag在 [Docker Hub](https://hub.docker.com/r/lejianwen/rustdesk-server/) 上获得:
|
||
|
||
| 架构 | image:tag |
|
||
|---------| ----------------------------------------- |
|
||
| amd64 | `lejianwen/rustdesk-server:latest` |
|
||
| arm64v8 | `lejianwen/rustdesk-server:latest-arm64v8` |
|
||
|
||
您可以使用以下命令,直接通过 ``docker run`` 來启动这些镜像:
|
||
|
||
```bash
|
||
docker run --name hbbs --net=host -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbs -r <relay-server-ip[:port]>
|
||
docker run --name hbbr --net=host -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbr
|
||
```
|
||
|
||
或不使用 `--net=host` 参数启动, 但这样 P2P 直连功能将无法工作。
|
||
|
||
对于使用了 SELinux 的系统,您需要将 ``/root`` 替换为 ``/root:z``,以保证容器的正常运行。或者,也可以通过添加参数 ``--security-opt label=disable`` 来完全禁用 SELinux 容器隔离。
|
||
|
||
```bash
|
||
docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbs -r <relay-server-ip[:port]>
|
||
docker run --name hbbr -p 21117:21117 -p 21119:21119 -v "$PWD/data:/root" -d lejianwen/rustdesk-server:latest hbbr
|
||
```
|
||
|
||
`relay-server-ip` 参数是运行这些容器的服务器的 IP 地址(或 DNS 名称)。如果你不想使用 **21117** 作为 `hbbr` 的服务端口,可使用可选参数 `port` 进行指定。
|
||
|
||
您也可以使用 docker-compose 进行构建,以下为配置示例:
|
||
|
||
```yaml
|
||
version: '3'
|
||
|
||
networks:
|
||
rustdesk-net:
|
||
external: false
|
||
|
||
services:
|
||
hbbs:
|
||
container_name: hbbs
|
||
ports:
|
||
- 21115:21115
|
||
- 21116:21116
|
||
- 21116:21116/udp
|
||
- 21118:21118
|
||
image: lejianwen/rustdesk-server:latest
|
||
command: hbbs -r rustdesk.example.com:21117
|
||
volumes:
|
||
- ./data:/root
|
||
networks:
|
||
- rustdesk-net
|
||
depends_on:
|
||
- hbbr
|
||
restart: unless-stopped
|
||
|
||
hbbr:
|
||
container_name: hbbr
|
||
ports:
|
||
- 21117:21117
|
||
- 21119:21119
|
||
image: lejianwen/rustdesk-server:latest
|
||
command: hbbr
|
||
volumes:
|
||
- ./data:/root
|
||
networks:
|
||
- rustdesk-net
|
||
restart: unless-stopped
|
||
```
|
||
|
||
编辑第16行来指定你的中继服务器 (默认端口监听在 21117 的那一个)。 如果需要的话,您也可以编辑 volume 信息 (第 18 和 33 行)。
|
||
|
||
(感谢 @lukebarone 和 @QuiGonLeong 协助提供的 docker-compose 配置示例)
|
||
|
||
## 基于 S6-overlay 的镜像
|
||
|
||
> 这些镜像是针对 `busybox:stable` 构建的,并添加了可执行程序(hbbr 和 hbbs)以及 [S6-overlay](https://github.com/just-containers/s6-overlay)。 它们可以使用以下tag在 [Docker hub](https://hub.docker.com/r/lejianwen/rustdesk-server-s6/) 上获取:
|
||
|
||
|
||
| 架構 | version | image:tag |
|
||
| --------- | ------- | -------------------------------------------- |
|
||
| multiarch | latest | `lejianwen/rustdesk-server-s6:latest` |
|
||
| amd64 | latest | `lejianwen/rustdesk-server-s6:latest-amd64` |
|
||
| i386 | latest | `lejianwen/rustdesk-server-s6:latest-i386` |
|
||
| arm64v8 | latest | `lejianwen/rustdesk-server-s6:latest-arm64v8` |
|
||
| armv7 | latest | `lejianwen/rustdesk-server-s6:latest-armv7` |
|
||
| multiarch | 2 | `lejianwen/rustdesk-server-s6:2` |
|
||
| amd64 | 2 | `lejianwen/rustdesk-server-s6:2-amd64` |
|
||
| i386 | 2 | `lejianwen/rustdesk-server-s6:2-i386` |
|
||
| arm64v8 | 2 | `lejianwen/rustdesk-server-s6:2-arm64v8` |
|
||
| armv7 | 2 | `lejianwen/rustdesk-server-s6:2-armv7` |
|
||
| multiarch | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0` |
|
||
| amd64 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-amd64` |
|
||
| i386 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-i386` |
|
||
| arm64v8 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-arm64v8` |
|
||
| armv7 | 2.0.0 | `lejianwen/rustdesk-server-s6:2.0.0-armv7` |
|
||
|
||
强烈建议您使用`major version` 或 `latest` tag 的 `multiarch` 架构的镜像。
|
||
|
||
S6-overlay 在此处作为监控程序,用以保证两个进程的运行,因此使用此镜像,您无需运行两个容器。
|
||
|
||
您可以使用 `docker run` 命令直接启动镜像,如下:
|
||
|
||
```bash
|
||
docker run --name rustdesk-server \
|
||
--net=host \
|
||
-e "RELAY=rustdeskrelay.example.com" \
|
||
-e "ENCRYPTED_ONLY=1" \
|
||
-v "$PWD/data:/data" -d lejianwen/rustdesk-server-s6:latest
|
||
```
|
||
|
||
或刪去 `--net=host` 参数, 但 P2P 直连功能将无法工作。
|
||
|
||
```bash
|
||
docker run --name rustdesk-server \
|
||
-p 21115:21115 -p 21116:21116 -p 21116:21116/udp \
|
||
-p 21117:21117 -p 21118:21118 -p 21119:21119 \
|
||
-e "RELAY=rustdeskrelay.example.com" \
|
||
-e "ENCRYPTED_ONLY=1" \
|
||
-v "$PWD/data:/data" -d lejianwen/rustdesk-server-s6:latest
|
||
```
|
||
|
||
或着您也可以使用 docker-compose 文件:
|
||
|
||
```yaml
|
||
version: '3'
|
||
|
||
services:
|
||
rustdesk-server:
|
||
container_name: rustdesk-server
|
||
ports:
|
||
- 21114:21114
|
||
- 21115:21115
|
||
- 21116:21116
|
||
- 21116:21116/udp
|
||
- 21117:21117
|
||
- 21118:21118
|
||
- 21119:21119
|
||
image: lejianwen/rustdesk-server-s6:latest
|
||
environment:
|
||
- "RELAY=rustdesk.example.com:21117"
|
||
- "ENCRYPTED_ONLY=1"
|
||
volumes:
|
||
- ./data:/data
|
||
restart: unless-stopped
|
||
```
|
||
|
||
对于此容器镜像,除了在下面的环境变量部分指定的变量之外,您还可以使用以下`环境变量`
|
||
|
||
| 环境变量 | 是否可选 | 描述 |
|
||
|----------------|------|--------------------------|
|
||
| RELAY | 否 | 运行此容器的宿主机的 IP 地址/ DNS 名称 |
|
||
| ENCRYPTED_ONLY | 是 | 如果设置为 **"1"**,将不接受未加密的连接。 |
|
||
| KEY_PUB | 是 | 密钥对中的公钥(Public Key) |
|
||
| KEY_PRIV | 是 | 密钥对中的私钥(Private Key) |
|
||
|
||
### 基于 S6-overlay 镜像的密钥管理
|
||
|
||
您可以将密钥对保存在 Docker volume 中,但我们建议不要将密钥写入文件系統中;因此,我们提供了一些方案。
|
||
|
||
在容器启动时,会检查密钥对是否存在(`/data/id_ed25519.pub` 和 `/data/id_ed25519`),如果其中一個密钥不存在,则会从环境变量或 Docker Secret 中重新生成它。
|
||
然后检查密钥对的可用性:如果公钥和私钥不匹配,容器将停止运行。
|
||
如果您未提供密钥,`hbbs` 将会在默认位置生成一个。
|
||
|
||
#### 使用 ENV 存储密钥对
|
||
|
||
您可以使用 Docker 环境变量來存储密钥。如下:
|
||
|
||
```bash
|
||
docker run --name rustdesk-server \
|
||
--net=host \
|
||
-e "RELAY=rustdeskrelay.example.com" \
|
||
-e "ENCRYPTED_ONLY=1" \
|
||
-e "DB_URL=/db/db_v2.sqlite3" \
|
||
-e "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ==" \
|
||
-e "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE=" \
|
||
-v "$PWD/db:/db" -d lejianwen/rustdesk-server-s6:latest
|
||
```
|
||
|
||
```yaml
|
||
version: '3'
|
||
|
||
services:
|
||
rustdesk-server:
|
||
container_name: rustdesk-server
|
||
ports:
|
||
- 21114:21114
|
||
- 21115:21115
|
||
- 21116:21116
|
||
- 21116:21116/udp
|
||
- 21117:21117
|
||
- 21118:21118
|
||
- 21119:21119
|
||
image: lejianwen/rustdesk-server-s6:latest
|
||
environment:
|
||
- "RELAY=rustdesk.example.com:21117"
|
||
- "ENCRYPTED_ONLY=1"
|
||
- "DB_URL=/db/db_v2.sqlite3"
|
||
- "KEY_PRIV=FR2j78IxfwJNR+HjLluQ2Nh7eEryEeIZCwiQDPVe+PaITKyShphHAsPLn7So0OqRs92nGvSRdFJnE2MSyrKTIQ=="
|
||
- "KEY_PUB=iEyskoaYRwLDy5+0qNDqkbPdpxr0kXRSZxNjEsqykyE="
|
||
volumes:
|
||
- ./db:/db
|
||
restart: unless-stopped
|
||
```
|
||
|
||
#### 使用 Docker Secret 來保存密钥对
|
||
|
||
您还可以使用 Docker Secret 來保存密钥。
|
||
如果您使用 **docker-compose** 或 **docker swarm**,推荐您使用。
|
||
只需按照以下示例操作:
|
||
|
||
```bash
|
||
cat secrets/id_ed25519.pub | docker secret create key_pub -
|
||
cat secrets/id_ed25519 | docker secret create key_priv -
|
||
docker service create --name rustdesk-server \
|
||
--secret key_priv --secret key_pub \
|
||
--net=host \
|
||
-e "RELAY=rustdeskrelay.example.com" \
|
||
-e "ENCRYPTED_ONLY=1" \
|
||
-e "DB_URL=/db/db_v2.sqlite3" \
|
||
--mount "type=bind,source=$PWD/db,destination=/db" \
|
||
lejianwen/rustdesk-server-s6:latest
|
||
```
|
||
|
||
```yaml
|
||
version: '3'
|
||
|
||
services:
|
||
rustdesk-server:
|
||
container_name: rustdesk-server
|
||
ports:
|
||
- 21114:21114
|
||
- 21115:21115
|
||
- 21116:21116
|
||
- 21116:21116/udp
|
||
- 21117:21117
|
||
- 21118:21118
|
||
- 21119:21119
|
||
image: lejianwen/rustdesk-server-s6:latest
|
||
environment:
|
||
- "RELAY=rustdesk.example.com:21117"
|
||
- "ENCRYPTED_ONLY=1"
|
||
- "DB_URL=/db/db_v2.sqlite3"
|
||
volumes:
|
||
- ./db:/db
|
||
restart: unless-stopped
|
||
secrets:
|
||
- key_pub
|
||
- key_priv
|
||
|
||
secrets:
|
||
key_pub:
|
||
file: secrets/id_ed25519.pub
|
||
key_priv:
|
||
file: secrets/id_ed25519
|
||
```
|
||
|
||
## 如何生成密钥对
|
||
|
||
加密需要一对密钥;您可以按照前面所述提供它,但需要一个工具去生成密钥对。
|
||
|
||
您可以使用以下命令生成一对密钥:
|
||
|
||
```bash
|
||
/usr/bin/rustdesk-utils genkeypair
|
||
```
|
||
|
||
如果您沒有(或不想)在系统上安装 `rustdesk-utils` 套件,您可以使用 Docker 执行相同的命令:
|
||
|
||
```bash
|
||
docker run --rm --entrypoint /usr/bin/rustdesk-utils lejianwen/rustdesk-server-s6:latest genkeypair
|
||
```
|
||
|
||
运行后的输出内容如下:
|
||
|
||
```text
|
||
Public Key: 8BLLhtzUBU/XKAH4mep3p+IX4DSApe7qbAwNH9nv4yA=
|
||
Secret Key: egAVd44u33ZEUIDTtksGcHeVeAwywarEdHmf99KM5ajwEsuG3NQFT9coAfiZ6nen4hfgNICl7upsDA0f2e/jIA==
|
||
```
|
||
|
||
## .deb 套件
|
||
|
||
每个可执行文件都有单独的 .deb 套件可供使用,您可以在 [releases](https://github.com/lejianwen/rustdesk-server/releases) 页面中找到它們。
|
||
這些套件适用于以下发行版:
|
||
|
||
- Ubuntu 22.04 LTS
|
||
- Ubuntu 20.04 LTS
|
||
- Ubuntu 18.04 LTS
|
||
- Debian 11 bullseye
|
||
- Debian 10 buster
|
||
|
||
## ENV 环境变量
|
||
|
||
可以使用这些`环境变量`参数來配置 hbbs 和 hbbr。
|
||
您可以像往常一样指定参数,或者使用 .env 文件。
|
||
|
||
| 参数 | 可执行文件 | 描述 |
|
||
|-----------------------|---------------|--------------------------------------------------|
|
||
| ALWAYS_USE_RELAY | hbbs | 如果设定为 **"Y"**,将关闭直接点对点连接功能 |
|
||
| DB_URL | hbbs | 数据库配置 |
|
||
| DOWNGRADE_START_CHECK | hbbr | 降级检查之前的延迟是啊尽(以秒为单位) |
|
||
| DOWNGRADE_THRESHOLD | hbbr | 降级检查的阈值(bit/ms) |
|
||
| KEY | hbbs/hbbr | 如果设置了此参数,将强制使用指定密钥对,如果设为 **"_"**,则强制使用任意密钥 |
|
||
| LIMIT_SPEED | hbbr | 速度限制(以Mb/s为单位) |
|
||
| PORT | hbbs/hbbr | 监听端口(hbbs为21116,hbbr为21117) |
|
||
| RELAY_SERVERS | hbbs | 运行hbbr的机器的IP地址/DNS名称(用逗号分隔) |
|
||
| RUST_LOG | all | 设置 debug level (error\|warn\|info\|debug\|trace) |
|