{{H1}}
Infrastructure Runbook · Oracle Cloud 新加坡
记录自建 RustDesk 远程桌面服务器的架构、配置、问题诊断过程与结论,供 AI 快速读懂上下文、本人长期维护迭代。
所有连接均为电信→电信,旧「电信→联通 ISP 封锁」结论作废;已验证直连:C→D(rust-d.<主域名>:21118);A/C 的 IPv6 已因换网变更(A 由 DDNS 自动跟踪,C 待装 DDNS);B→A、D→C、D→B 待重测(预期可通,见待办 ㉑);直连模式不消耗 Oracle 流量;中继可用(注意 Screen Inception,需先退出 A 上的 ToDesk);信令 / 中继服务器已切换为域名 rd.<主域名>。
0文档维护说明(给未来的我 / AI)
- 每次改动配置或排查,请在文末 「变更与迭代日志」 追加一条,并更新顶部「最后更新」与「当前状态」。
- 状态图标约定:🟢 正常 🟡 部分可用 · 待优化 🔴 故障
- 命令、IP、端口等关键信息以代码块记录,方便复制和 AI 解析。
- 本文档为脱敏版:服务器 IP、IPv6 地址、设备 ID 均以占位符呈现,真实值在本机
private/secrets.json。
1架构概览
| 项目 | 内容 |
|---|---|
| 软件 | RustDesk(开源远程桌面,自建中继 / 信令服务器) |
| 服务器 | Oracle Cloud 免费实例,地域:新加坡 |
| 系统 | Ubuntu(实例名 Oracle 实例名,登录用户 ubuntu) |
| 服务进程 | hbbs(信令 / ID Server)、hbbr(中继 / Relay Server) |
| 主控端 | 本人电脑,家庭宽带 1000M(中国,普通家宽) |
| 被控端 | ⚠️ 待补充(在哪条网络尚未确认) |
| 连接现状 | 加密中继连接 (TCP) —— 即走 Relay,未实现 P2P 直连 |
数据路径示意
服务器仅做信令,画质好
国际绕路,又卡又糊
2原始诉求(问题)
- 界面模糊、卡顿 —— 原因是什么,如何解决。
- 用二级域名代替 IP —— 是否更好、怎么配。
3诊断结论速览(TL;DR)
卡顿 / 模糊根因
连接走了 Relay 中继,所有画面流量绕道新加坡服务器(免费实例带宽小 + 国内↔新加坡国际链路瓶颈)。
服务器侧已 100% 排除
防火墙端口、服务进程、UDP 监听全部正常。
真正卡点
NAT 打洞失败,两端大概率都是 CGNAT 大内网(中国家宽通病),UDP 打不通 → 被迫退回中继。
1000M 宽带无用武之地
走中继时瓶颈在服务器出口和国际链路,本地带宽再大也没用。
域名问题
建议用域名,但只提升「运维便利性」,不会改善画质 / 速度。
4已验证的服务器配置(均正常 ✅)
4.1 UFW 防火墙(端口已全部放通)
实例用的是 UFW,不是 iptables。Oracle 云控制台安全列表已设为 all。
21115:21119/tcp ALLOW IN Anywhere ✅
21116/udp ALLOW IN Anywhere ✅ 打洞关键端口
(v6 规则同样已放通)- 另有 Fail2Ban 自动封禁大量恶意 SSH IP(正常防护,无需处理)。
- 已开放的其他端口:22 (SSH)、80、443、8633 (tcp/udp);2096 为 DENY。
4.2 服务进程与监听
sudo ss -tulnp | grep 2111
udp *:21116 hbbs (pid 3954199) ✅ 打洞 UDP 在监听
tcp *:21115 hbbs
tcp *:21116 hbbs
tcp *:21117 hbbr
tcp *:21118 hbbs
tcp *:21119 hbbr结论:服务端完全正常,无需再动。
4.3 端口职责对照表
| 端口 | 协议 | 进程 | 作用 |
|---|---|---|---|
21115 | TCP | hbbs | NAT 类型测试 |
21116 | TCP + UDP | hbbs | 21116/UDP = NAT 打洞核心;TCP 为信令心跳 |
21117 | TCP | hbbr | 中继 Relay |
21118 | TCP | hbbs | Web 客户端支持 |
21119 | TCP | hbbr | Web 客户端中继 |
5已解决:IPv6 直连 ✅ 2026-06-13
根因确认
服务器侧无误,打洞失败根本原因是双端均为 CGNAT(IPv4 层),但两端均有公网 IPv6,IPv6 无 NAT,可直接 P2P。
各端 IPv6 信息
| 端 | 公网 IPv6 | 运营商 |
|---|---|---|
| A 机 | A 机 IPv6 2026-07-05 换电信后本机实测; rust-a.<主域名> DDNS 自动跟踪,原联通地址已失效 | 中国电信(与 D 同 /64 前缀,疑同一条宽带) |
| B 机 | B 机 IPv6 | 中国电信 |
| C 机 | 待查 原联通地址已失效;换电信后未记录,待装 DDNS 时更新 | 中国电信 |
| D 机 | 与 A 同 /64 前缀 最新地址以 rust-d.<主域名> AAAA 记录为准,DDNS 自动更新 | 中国电信 |
| 服务器 | Oracle 服务器 IPv6 | Oracle 新加坡 |
当前生效的连接方式(域名直连,双向互控)
两台机器均可互为主控 / 被控,连接时填域名即可,IP 变化自动跟踪。
| 方向 | 连接栏填写 |
|---|---|
| A 机控制 B 机 | rust-b.<主域名>:21118 |
| B 机控制 A 机 | rust-a.<主域名>:21118 |
| A 机控制 C 机 | ⚠️ 待重配 原联通临时 IP 已失效,待 C 装 DDNS 后改用 rust-c.<主域名>:21118 |
| C 机控制 D 机 ✅ | rust-d.<主域名>:21118 |
各端配置详情
A 机 电信
- 2026-07-05 由联通换为电信
- 域名:
rust-a.<主域名>(AAAA 自动更新) - 设置 → 安全 → 「允许 IP 直接访问」 ✅ 端口 21118
- 防火墙入站 TCP 21118 已放行
- DDNS 脚本:
C:\Scripts\update-rustdesk-ipv6.ps1 - 计划任务:
RustDesk-DDNS-Boot(开机)+RustDesk-DDNS-Repeat(每 1 小时)
B 机 电信
- 域名:
rust-b.<主域名>(AAAA 自动更新) - 设置 → 安全 → 「允许 IP 直接访问」 ✅ 端口 21118
- 防火墙入站 TCP 21118 已放行
- DDNS 脚本:
C:\Scripts\update-rustdesk-ipv6.ps1 - 计划任务:Boot + Repeat(每 1 小时)
- Cloudflare API Token 名称:DDNS Token 名称(DNS Write,无过期)
- RustDesk 服务:✅ 已安装(
RustDesk ServiceRunning) - RustDesk ID:B 机 RustDesk ID(2026-06-14 重置后的新 ID)
- ⚠️ 永久密码已重置:
RustDesk.toml被删除,需重新在 B 的设置里设置永久密码
D 机 电信
- 域名:
rust-d.<主域名>(AAAA 由脚本自动创建并更新) - 设置 → 安全 → 「允许 IP 直接访问」 ✅ 端口 21118
- 防火墙入站 TCP 21118 已放行
- DDNS 脚本:与 A/B 同款,仅
$SUBDOMAIN改为rust-d.<主域名> - 计划任务:Boot + Repeat(每 1 小时;改间隔命令见待办 ⑮)
- 已验证:C→D 域名直连 ✅(2026-07-05)
DDNS 日志查看
Get-Content C:\Scripts\ddns.log -Tail 105.1已确认:B→A 直连单向失败 2026-06-14 · 历史
当时 A 为联通,失败根因是电信→联通跨运营商封锁;现 A 已换为电信宽带,该前提不复存在,B→A 需按电信→电信重测(见待办 ㉑)。下文保留作历史记录。
现象
| 方向 | 结果 |
|---|---|
| A(联通)→ B(电信)直连 | ✅ 成功 P2P 直连 |
| B(电信)→ A(联通)直连 | ❌ 失败 连接不通 |
结论
疑为电信侧单向封锁:电信 IPv6 出方向对联通 21118 端口有过滤或运营商 ACL 拦截,但联通→电信方向正常。此问题在用户侧无法修复。
当前 B→A 的可用替代方案
- 让 A 主动连 B(A→B 直连正常)—— 需要在 A 端操作,不能从 B 发起。
- B→A 走中继(Relay 模式)—— 可用,但有新 Bug,见 5.2。
5.2已定位:中继时画面套娃(Screen Inception)根因 ✅ 2026-06-14
复现路径
- 用 ToDesk 远程连入 B(电信)
- 在 B 上打开 RustDesk,用 A 的 RustDesk ID 中继连接 A(联通)
- 连接成功后,画面出现多层叠加任务栏,无限嵌套闪烁
根因(已截图确认)
屏幕捕获递归(Screen Inception),不是 Bug,是物理规律:
A 上的 ToDesk 是循环的关键节点。只要 A 有 ToDesk 会话开着,B 通过 RustDesk 看到的 A 就会包含 B 自己的内容。
解决方案
连接前在 A 上关掉 ToDesk(退出,不是最小化)
- 通过 B 的 RustDesk 窗口点进 A 的桌面
- 右键 A 任务栏托盘中的 ToDesk 图标 → 退出
- 或 Ctrl + Alt + Del → 任务管理器 → 结束 ToDesk 进程
- ToDesk 关闭后循环立即断开,画面稳定
正确工作流(B 控制 A,无套娃)
- 物理到 B 旁边(或通过不涉及 A 屏幕的方式控制 B)
- 确认 A 上 ToDesk 已退出(无活跃会话)
- 在 B 的 RustDesk 里输入 A 的 ID(A 机 RustDesk ID)→ 连接
- 画面正常,无套娃
待 B 旁边时验证,目前尚未实地测试(2026-06-14)。
6客户端画质优化(无论直连 / 中继都先做)
参考 RustDesk 远程窗口顶部工具栏菜单:
| 设置项 | 当前 | 建议 |
|---|---|---|
| 缩放 | 适应窗口 | 改为 原始尺寸(消除拉伸模糊) |
| 画质 | 平衡(默认) | 高清 或自定义拉高码率 |
| 编解码 | — | 选 硬件编码 H264 / H265(两端需支持) |
| 显示质量监控 | 未勾 | 勾选,实时看码率 / 帧率 / 延迟 |
7信令 / 中继服务器域名化 ✅ 2026-06-14 已完成
✅ 好处:IP 变了只改一条 DNS,不用动所有客户端;好记好分发;便于后续上 TLS / 反代。 ❌ 不会让画面变快或变清晰(DNS 解析开销可忽略)。
已生效配置 · Cloudflare DNS 记录
| Type | Name | IPv4 | Proxy | TTL |
|---|---|---|---|---|
| A | rd | Oracle 服务器 IP | 灰云 DNS only | Auto |
必须灰云,橙色代理会拦截 21116-21119 端口。
A 机 + B 机 RustDesk 配置文件(已批量替换)
配置文件路径:%APPDATA%\RustDesk\config\RustDesk2.toml
# 执行命令(A 机和 B 机各执行一次,管理员 PowerShell)
$config = "$env:APPDATA\RustDesk\config\RustDesk2.toml"
(Get-Content $config) `
-replace "rendezvous_server = '<旧IP>:21116'", "rendezvous_server = 'rd.<主域名>:21116'" `
-replace "relay-server = '<旧IP>'", "relay-server = 'rd.<主域名>'" `
-replace "custom-rendezvous-server = '<旧IP>'", "custom-rendezvous-server = 'rd.<主域名>'" |
Set-Content $config
Restart-Service RustDesk验证:
Get-Content "$env:APPDATA\RustDesk\config\RustDesk2.toml" | Select-String "server"
# 三行均应显示 rd.<主域名>,RustDesk 界面底部状态显示「就绪」7.1服务器 Key 轮换标准流程 ✅ 2026-07-05 已验证
RustDesk 的 Key = hbbs 自动生成的 ed25519 密钥对(id_ed25519 私钥 + id_ed25519.pub 公钥),客户端填的是公钥内容。 本服务器为 Docker 部署:容器 hbbs / hbbr(镜像 rustdesk/rustdesk-server:latest),数据卷 /home/ubuntu/rustdesk/data → 容器内 /root,密钥和 ID 数据库(db_v2.sqlite3)均持久化在宿主机该目录,重建容器不丢数据。
服务器端(SSH 到实例)
# ① 备份并删除旧密钥(在宿主机数据卷目录操作)
cd /home/ubuntu/rustdesk/data
sudo mv id_ed25519 id_ed25519.bak
sudo mv id_ed25519.pub id_ed25519.pub.bak
# ② 重启容器,hbbs 发现无密钥会自动生成新的一对
sudo docker restart hbbs hbbr
# ③ 获取新公钥(这一串就是发给客户端的新 Key,44 字符左右,以 = 结尾)
sudo cat /home/ubuntu/rustdesk/data/id_ed25519.pub
# ④ 验证服务正常:应有 6 行监听(21115-21119 TCP + 21116 UDP)
sudo ss -tulnp | grep 2111把 .bak 两个文件改回原名,再 sudo docker restart hbbs hbbr。
客户端(A / B / C / D 四台都要更新)
否则通过 ID 的信令 / 中继连不上。
方式一(图形界面):RustDesk → 设置 → 网络 → 解锁 → Key 栏粘贴新公钥(ID 服务器保持 rd.<主域名>)。
方式二(管理员 PowerShell 批量替换):
$config = "$env:APPDATA\RustDesk\config\RustDesk2.toml"
(Get-Content $config) -replace "key = '.*'", "key = '<新公钥>'" | Set-Content $config
Restart-Service RustDesk验证:每台 RustDesk 主界面底部显示「就绪」。
影响范围
| 是否受影响 | 路径 |
|---|---|
| ✅ 受影响 | 通过 ID 连接的信令 / 中继路径(如 B→A 中继)—— 客户端未更新 Key 前连不上 |
| ❌ 不受影响 | IPv6 域名直连(rust-x.<主域名>:21118)不经过 hbbs / hbbr,Key 变更无感 |
8关键信息速查
本节所有敏感值均为占位符。需要真实值时在本机执行 python build.py --private。
| 项 | 值 |
|---|---|
| 服务器地域 | 新加坡(Oracle Cloud 免费实例) |
| 服务器公网 IP | Oracle 服务器 IP |
| 服务器公网 IPv6 | Oracle 服务器 IPv6 |
| 信令 / 中继域名 | rd.<主域名>(Cloudflare A 记录,灰云 DNS only) |
| A 机直连域名 | rust-a.<主域名>(AAAA 记录,DDNS 自动更新) |
| B 机直连域名 | rust-b.<主域名>(AAAA 记录,DDNS 自动更新) |
| D 机直连域名 | rust-d.<主域名>(AAAA 记录,DDNS 自动更新) |
| SSH 登录 | ubuntu@Oracle 实例名 |
| RustDesk Key(公钥) | 不入档 2026-07-05 已轮换,取值见服务器 /home/ubuntu/rustdesk/data/id_ed25519.pub |
| A 机 RustDesk ID | A 机 RustDesk ID |
| B 机 RustDesk ID | B 机 RustDesk ID |
| 部署方式 | Docker(容器 hbbs / hbbr,镜像 rustdesk/rustdesk-server:latest,数据卷 /home/ubuntu/rustdesk/data → /root) |
| hbbs PID(参考) | 2504031(2026-07-05 换 Key 重启后) |
| hbbr PID(参考) | 2504115(2026-07-05 换 Key 重启后) |
9变更与迭代日志
按时间升序记录,最新一条在最下方。
每次改动复制下面这行追加到上表: | YYYY-MM-DD | 做了什么改动 / 排查了什么 | 🟢/🟡/🔴 |
10待办事项
完成一项就把复选框勾上(- [ ] 改成 - [x]),并到第 9 节日志追加一行。 标记约定:[x] 已完成 · [ ] 待办 · [!] 阻塞 · [~] 已并入其他条目
🔴 当前阻塞:让连接从「中继」变「直连」
- ✓① 确认被控端所在网络 —— 中国电信家宽,有公网 IPv6。
- ✓② 两端各测 IPv6 —— 主控联通 / 被控电信 / 服务器 Oracle,三端均有公网 IPv6。
- ✓③ IPv6 直连验证 —— 采用直接 IP 直连方式,连接成功,P2P 直连实现。
- ✓⑤ 验收 —— 直连成功,不再走中继。
- ✓⑨ IPv6 DDNS 稳定化 —— 双端均已配置 Cloudflare DDNS + Windows 计划任务,连接改用域名,IP 变化自动跟踪。
🟡 优化项(直连成功后或并行做)
- ⑥ 客户端画质 —— 缩放改「原始尺寸」+ 画质「高清」+ 开硬件编码 H264/H265 + 勾「显示质量监控」。
- ✓⑦ 二级域名 ——
rd.<主域名>A 记录已建(灰云),A/B 机RustDesk2.toml已全部替换,验证通过。
🖥️ 扩展:C 机和 D 机接入
- ✓⑩ 确认 C / D 机网络 —— C 机联通(IPv6 已记录);D 机电信(非 B 机)。
- ✓⑪ 确认子域名命名 ——
rust-c.<主域名>/rust-d.<主域名>。 - ⑫ C 机装 DDNS —— ⚠️ C 已换电信,IPv6 已变,原联通地址和「A→C 已验证」均失效。装 DDNS(脚本同 A/B/D,
$SUBDOMAIN改rust-c.<主域名>)+ 计划任务后,连接改用rust-c.<主域名>:21118并重测。 - ✓⑬ D 机装 DDNS —— 已完成(2026-07-05):DDNS 脚本 + 计划任务已装,C 机用
rust-d.<主域名>:21118域名直连验证成功。 - !㉑ 全电信后重测各方向直连 —— 四台已全为电信,旧跨运营商封锁结论作废,预期两两可通:B→A(填
rust-a.<主域名>:21118)、D→C(待 C 装 DDNS 后填rust-c.<主域名>:21118)、D→B(填rust-b.<主域名>:21118,即原 ⑲)。全部通过后 ⑭ 验收即可关闭。 - ⑭ 验收 —— 四台机器两两连接测试,确认均为直连 P2P。
- –⑲ 测试电信→电信直连(D→B) —— 已并入 ㉑(四台全电信后统一重测)。
🗑️ 可以清理的任务
- ⑳ 删除 A 机的 DDNS 定时任务 —— A 机永远只作主控端,不需要被远程控制。可删除
RustDesk-DDNS-Boot和RustDesk-DDNS-Repeat两个计划任务及 DDNS 脚本。B 机的同名任务必须保留(A 连 B 依赖它)。⚠️ 2026-07-05:建议暂缓 —— A 换电信后 B→A 直连可能恢复(㉑ 待测),若测通且需要反向控制 A,则 A 的 DDNS 必须保留。
🔧 可选优化
- ✓⑮ 调整 DDNS 检测间隔为 1 小时(IPv6 地址基本不变,1 小时完全够用,已执行)。
# A 机和 B 机各执行一次(/ri 单位:分钟)
schtasks /change /tn "RustDesk-DDNS-Repeat" /ri 60改为更长间隔(一天 / 一周 / 一月):schtasks /change 改不了调度类型(/sc),需用 /f 覆盖重建任务。在目标机器管理员 PowerShell 执行下面其中一条即可:
# 每天一次(每天 09:00)
schtasks /create /tn "RustDesk-DDNS-Repeat" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\update-rustdesk-ipv6.ps1" /sc daily /mo 1 /st 09:00 /ru SYSTEM /rl HIGHEST /f
# 每周一次(每周一 09:00)
schtasks /create /tn "RustDesk-DDNS-Repeat" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\update-rustdesk-ipv6.ps1" /sc weekly /d MON /st 09:00 /ru SYSTEM /rl HIGHEST /f
# 每月一次(每月 1 号 09:00)
schtasks /create /tn "RustDesk-DDNS-Repeat" /tr "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Scripts\update-rustdesk-ipv6.ps1" /sc monthly /mo 1 /d 1 /st 09:00 /ru SYSTEM /rl HIGHEST /f
# 改完验证
schtasks /query /tn "RustDesk-DDNS-Repeat" /v /fo list间隔越长,IPv6 变更后 DNS 失效的窗口越长(例如每月一次,IP 变了最坏要等一个月才自动修正)。好在 RustDesk-DDNS-Boot 开机必跑一次,重启机器即可立即修正;连不上时也可在被控端手动跑一次脚本。建议不低于每天一次。
🔴 B→A 连接问题
- ✓⑯ 排查 B→A 单向失败根因 —— 已确认:ISP 封锁(电信→联通 IPv6 路由不通),用户侧无法修复。
- ⑰ 验证 Screen Inception 修复 —— 待实地到 B 旁边测试:连接前在 A 上退出 ToDesk,再从 B 用 A 的 ID 中继连 A,确认画面正常。
- ⑱ 重设 B 的 RustDesk 永久密码 —— B 的
RustDesk.toml被误删,密码已重置,需在 B 的 RustDesk → 设置 → 安全 → 重新设置永久密码。
📝 文档待补全
- ⑧ 补全第 8 节速查表 —— 服务器公网 IP、域名、Key 等占位信息(Key 注意脱敏勿外泄)。