[feat] add IPv6 support for direct IP connections, refs #129

This commit is contained in:
dijunkun
2026-10-29 20:02:43 +08:00
parent f09a4c50d1
commit 7c6e14989f
18 changed files with 287 additions and 187 deletions
+6 -4
View File
@@ -52,20 +52,22 @@ sudo apt install "./crossdesk-virtual-desktop-xfce-linux-amd64-<version>.deb"
### 通过局域网 IP 直连
当前 IP 直连入口用于 Windows、macOS、Linux 的普通桌面客户端。两台电脑的 IPv4 地址需要可互相访问;有无外网均可,不需要等待连接信令服务器。浏览器、移动端、Windows 登录前服务及 Linux 无头控制台目前继续使用设备 ID 连接流程。
当前 IP 直连入口用于 Windows、macOS、Linux 的普通桌面客户端。两台电脑的 IPv4 或 IPv6 地址需要可互相访问;有无外网均可,不需要等待连接信令服务器。浏览器、移动端、Windows 登录前服务及 Linux 无头控制台目前继续使用设备 ID 连接流程。
1. **启用并切换。** 两端在 **设置 → 常规 → 网络连接** 中启用 **IP 直连** 并保存(默认开启),再用主窗口左上角按钮切换到 **IP 直连模式**。
2. **获取对方地址。** 被控端在“本机 IP / 端口”查看地址,复制按钮会复制完整的 `IP:端口`。有多个地址时,选择控制端能够访问的地址。
3. **连接并验证。** 控制端在“远端 IP / 端口”输入例如 `192.168.1.20:52888`,点击“连接”,再输入被控端此模式下的六位密码。省略端口时使用 **52888**;当前支持数字 IPv4,不支持主机名或 IPv6。
3. **连接并验证。** 控制端在“远端 IP / 端口”输入例如 `192.168.1.20:52888` 或 `[2001:db8::20]:52888`,点击“连接”,再输入被控端此模式下的六位密码。省略端口时使用 **52888**;支持数字 IPv4 和 IPv6,不支持主机名。
4. **保存与重连。** 密码弹窗可勾选“记住密码”。近期连接按 ID / IP 模式分别显示;不勾选也会保存设备记录,下次连接需重新输入密码。可从卡片重连、修改别名或删除记录。
<p align="center">
<img src="images/desktop-lan-password-zh.png" width="720" alt="IP 直连密码弹窗与记住密码选项,背景为演示局域网设备" />
</p>
**地址和端口:** 点击“切换 IP”选择展示与分享的地址,点击“修改端口”编辑该 IP 的端口。默认所有接口共用一个端口;关闭“监听所有 IPv4 接口”后,程序同时监听可用物理网卡上的 IPv4,每个 IP 单独保存端口。切换展示地址不会停用其他地址。首次为某个 IP 分配端口时优先使用 **52888**,被占用时自动分配并保存可用端口。已有配置的端口发生冲突时会提示,需手动修改。端口范围为 **1–65535**,修改失败会保留原设置;连接方需要使用界面显示的实际端口。局域网防火墙需允许 CrossDesk 的 TCP 协商连接和 UDP 远控流量。
**地址和端口:** 点击“切换 IP”选择展示与分享的地址,点击“修改端口”编辑该 IP 的端口。默认所有接口共用一个端口;关闭“监听所有 IP 接口”后,程序同时监听可用物理网卡上的 IPv4 和 IPv6,每个 IP 单独保存端口。切换展示地址不会停用其他地址。首次为某个 IP 分配端口时优先使用 **52888**,被占用时自动分配并保存可用端口。已有配置的端口发生冲突时会提示,需手动修改。端口范围为 **1–65535**,修改失败会保留原设置;连接方需要使用界面显示的实际端口。局域网防火墙需允许 CrossDesk 的 TCP 协商连接和 UDP 远控流量。
**所有接口监听:** 在 **设置 → 常规 → 网络连接** 中开启“监听所有 IPv4 接口”并保存,即可让有线、Wi-Fi、VPN 和虚拟网卡共用一个 TCP 协商端口。此选项默认开启,已有的显式关闭设置会保留;修改后点击“保存”生效,点击“取消”不改变监听,重启仍沿用已保存设置;关闭后恢复原来各物理网卡 IP 的端口。该模式下修改任一展示地址的端口,会修改所有接口共用的端口。“切换 IP”和复制仍使用本机实际地址,不能把 `0.0.0.0` 作为远端地址。新接口自动使用共同端口;VPN 两端仍须允许 TCP 和 UDP 互通。不支持 IPv6、回环远控或自动公网 NAT 穿透。切换监听范围会取消未完成认证的连接;已认证连接保留。若端口被占用或设置无法保存,切换失败并尝试恢复原监听,恢复失败的端口会显示错误并持续重试。
**所有接口监听:** 在 **设置 → 常规 → 网络连接** 中开启“监听所有 IP 接口”并保存,即可让有线、Wi-Fi、VPN 和虚拟网卡的 IPv4/IPv6 共用一个 TCP 协商端口。此选项默认开启,已有的显式关闭设置会保留;修改后点击“保存”生效,点击“取消”不改变监听,重启仍沿用已保存设置;关闭后恢复原来各物理网卡 IP 的端口。该模式下修改任一展示地址的端口,会修改所有接口共用的端口。“切换 IP”和复制仍使用本机实际地址,不能把 `0.0.0.0` 或 `::` 作为远端地址。新接口自动使用共同端口;VPN 两端仍须允许 TCP 和 UDP 互通。不支持回环远控或自动公网 NAT 穿透。切换监听范围会取消未完成认证的连接;已认证连接保留。若端口被占用或设置无法保存,切换失败并尝试恢复原监听,恢复失败的端口会显示错误并持续重试。
**IPv6 地址:** 可以输入裸 IPv6 使用默认端口,例如 `2001:db8::20`;指定端口时使用方括号,例如 `[2001:db8::20]:52888`。支持全局、ULA 和链路本地单播地址。链路本地地址须带连接端自己的网卡作用域,例如 `[fe80::1234%14]:52888`(也可使用系统支持的网卡名,如 `%en0`)。复制的地址带有被控端作用域,控制端连接时需换成控制端用于访问该链路的网卡编号或名称,两端编号不一定相同。程序仍使用实际地址执行 TCP 认证与 UDP 媒体协商;IPv4 客户端可以继续连接新版。操作系统不支持 IPv6 时,所有接口监听自动退回 IPv4;升级保留原有共享端口与开关状态。
**密码:** IP 直连使用独立的 **6 位英文字母或数字密码,区分大小写**。没有设置时自动生成,眼睛、刷新和铅笔按钮分别用于显示 / 隐藏、随机生成和自定义;修改成功后即时生效,无需重启或连接服务器。已保存旧密码的控制端需要重新输入。
+6 -4
View File
@@ -52,20 +52,22 @@ The eye button shows or hides the local password, the refresh button generates a
### Connect directly over LAN
The direct IP entry is available in the regular Windows, macOS, and Linux desktop clients. The computers must have mutually reachable IPv4 addresses. Internet access is optional, and there is no need to wait for a signaling server connection. Browsers, mobile clients, the Windows pre-sign-in service, and the Linux headless console currently use the device ID workflow.
The direct IP entry is available in the regular Windows, macOS, and Linux desktop clients. The computers must have mutually reachable IPv4 or IPv6 addresses. Internet access is optional, and there is no need to wait for a signaling server connection. Browsers, mobile clients, the Windows pre-sign-in service, and the Linux headless console currently use the device ID workflow.
1. **Enable and switch.** On both computers, select **Enable direct IP** under **Settings → General → Network** and save (enabled by default). Use the top-left button to switch to **Direct IP mode**.
2. **Get the host address.** Read **Local IP / Port** on the host. The copy button copies the full `IP:port`. If several addresses are available, choose one reachable from the controller.
3. **Connect and verify.** Enter an address such as `192.168.1.20:52888` under **Remote IP / Port**, click **Connect**, and enter the host's six-character password for this mode. Omitting the port uses **52888**. Only numeric IPv4 addresses are currently supported, not hostnames or IPv6.
3. **Connect and verify.** Enter an address such as `192.168.1.20:52888` or `[2001:db8::20]:52888` under **Remote IP / Port**, click **Connect**, and enter the host's six-character password for this mode. Omitting the port uses **52888**. Numeric IPv4 and IPv6 addresses are supported; hostnames are not.
4. **Save and reconnect.** Select **Remember password** in the password dialog if needed. Recent connections are separated by ID / IP mode. Device records are kept even without a saved password; you will be prompted next time. Cards let you reconnect, rename devices, or remove records.
<p align="center">
<img src="images/desktop-lan-password-en.png" width="720" alt="Direct IP password dialog with Remember password and demonstration LAN devices in the background" />
</p>
**Addresses and ports:** use **Switch IP** to choose the displayed address and **Edit port** to change its port. By default all interfaces share one port. When **Listen on all IPv4 interfaces** is disabled, the app listens on available physical-interface IPv4 addresses simultaneously and saves a separate port for each IP. Selecting a displayed address keeps the others listening. The first port assignment for an IP tries **52888**; if busy, it allocates and saves an available port. A conflict on an already saved port is reported and requires a manual change. Valid ports are **1–65535**; failed changes preserve the previous settings. Connect using the actual port displayed on the host. The LAN firewall must allow CrossDesk's TCP negotiation connection and UDP remote-control traffic.
**Addresses and ports:** use **Switch IP** to choose the displayed address and **Edit port** to change its port. By default all interfaces share one port. When **Listen on all IP interfaces** is disabled, the app listens on available physical-interface IPv4 and IPv6 addresses simultaneously and saves a separate port for each IP. Selecting a displayed address keeps the others listening. The first port assignment for an IP tries **52888**; if busy, it allocates and saves an available port. A conflict on an already saved port is reported and requires a manual change. Valid ports are **1–65535**; failed changes preserve the previous settings. Connect using the actual port displayed on the host. The LAN firewall must allow CrossDesk's TCP negotiation connection and UDP remote-control traffic.
**Listen on all interfaces:** enable **Listen on all IPv4 interfaces** under **Settings → General → Network** and save to share one TCP negotiation port across Ethernet, Wi-Fi, VPN, and virtual interfaces. This option is on by default; an explicitly saved off setting is preserved. Changes apply on Save and persist across restarts; Cancel leaves the listening scope unchanged. Turning it off restores the saved ports for individual physical-interface IPs. Editing the port of any displayed address in this mode changes the shared port. **Switch IP** and Copy still use actual local addresses; `0.0.0.0` is not a remote connection address. New interfaces use the shared port automatically. Both VPN endpoints must allow TCP and UDP traffic. IPv6, loopback remote control, and automatic public NAT traversal remain unsupported. Switching the listening scope cancels unfinished authentication but preserves authenticated connections. If binding or persistence fails, the app attempts to restore the previous listeners; any failed restoration is reported and retried.
**Listen on all interfaces:** enable **Listen on all IP interfaces** under **Settings → General → Network** and save to share one TCP negotiation port across IPv4 and IPv6 on Ethernet, Wi-Fi, VPN, and virtual interfaces. This option is on by default; an explicitly saved off setting is preserved. Changes apply on Save and persist across restarts; Cancel leaves the listening scope unchanged. Turning it off restores the saved ports for individual physical-interface IPs. Editing the port of any displayed address in this mode changes the shared port. **Switch IP** and Copy still use actual local addresses; neither `0.0.0.0` nor `::` is a remote connection address. New interfaces use the shared port automatically. Both VPN endpoints must allow TCP and UDP traffic. Loopback remote control and automatic public NAT traversal remain unsupported. Switching the listening scope cancels unfinished authentication but preserves authenticated connections. If binding or persistence fails, the app attempts to restore the previous listeners; any failed restoration is reported and retried.
**IPv6 addresses:** enter a bare address such as `2001:db8::20` for the default port, or `[2001:db8::20]:52888` for an explicit port. Global, ULA, and link-local unicast addresses are supported. Link-local addresses require the connecting computer's local interface zone, for example `[fe80::1234%14]:52888`, or an interface name supported by the OS such as `%en0`. A copied address contains the host's zone; replace it with the controller's interface index or name for that link, since the two machines may use different indices. TCP authentication and UDP media use the actual endpoints. Existing IPv4 clients remain compatible. All-interface listening falls back to IPv4 when the OS has no IPv6 support; upgrading preserves the shared port and saved preference.
**Passwords:** direct IP has its own **six-character, case-sensitive ASCII letter/digit password**. A missing password is generated automatically. Use the eye, refresh, and pencil buttons to show/hide, randomly generate, or customize it. A successful change applies immediately without restarting or connecting to a server. Controllers that saved the old password must enter the new one.
+13 -9
View File
@@ -12,11 +12,11 @@
| 项目 | ID 连接模式 | IP 直连模式 |
| --- | --- | --- |
| 用户输入 | 9 位设备 ID 和密码 | 数字 IPv4、可选端口和密码 |
| 用户输入 | 9 位设备 ID 和密码 | 数字 IPv4/IPv6、可选端口和密码 |
| 设备寻址 | 信令服务维护的设备及在线状态 | 直接连接指定 `IP:端口` |
| 密码验证位置 | 控制端提交 Join,由信令服务返回验证结果 | 被控端在本机验证收到的密码 |
| 信令通道 | 双方连接官方或自建服务的 WSS | 控制端直接连接被控端的 WSS |
| ICE 策略 | `ManagedServers`,使用服务端下发的 ICE 配置 | `HostOnly`,限定本次 WSS 连接的双方 IPv4 |
| ICE 策略 | `ManagedServers`,使用服务端下发的 ICE 配置 | `HostOnly`,限定本次 WSS 连接的双方 IPv4/IPv6 |
| 数据路径 | P2P 直连,或按配置与网络条件使用 TURN | 双方 UDP 直连,无 TURN 回退 |
| 原生数据加密要求 | 按对端能力协商 SRTP,保留旧端兼容分支,见第 4.1 节 | 必须支持视频、音频及数据 SRTP,否则拒绝建立会话 |
| 外网要求 | 取决于信令及 STUN/TURN 服务部署位置 | 直连会话不依赖外网服务 |
@@ -112,17 +112,19 @@ LAN 协商通道使用 TLS 1.3、路径 `/crossdesk/lan/v2` 和 WebSocket 子协
`GuiRuntime::InitializeLan()` 在功能启用且运行宿主支持时创建 `lan::SessionManager`。管理器加载本地 LAN 身份、证书、密码和端口配置;尚无密码时先异步生成六位英文字母或数字密码,准备完成后开始接受连接。
- `LocalIpv4Interfaces()` 枚举已启用接口上的非回环单播 IPv4,包含 VPN 和虚拟接口;关闭所有接口监听后的独立监听模式使用 `physical_only` 筛选物理网卡。当前只接收数字 IPv4 地址,不解析主机名或 IPv6。
- 管理器约每 2 秒刷新接口快照。默认使用单个 `0.0.0.0` 监听,界面可选择包括 VPN 在内的实际 IPv4 分享;在“设置 → 常规 → 网络连接”中关闭“监听所有 IPv4 接口”并保存后,改为按物理网卡地址分别监听,界面也只列出这些地址。通配地址只用于绑定,不作为连接目标或 ICE 候选。
- `LocalIpInterfaces()` 枚举已启用接口上的非回环单播 IPv4/IPv6,包含 VPN 和虚拟接口;关闭所有接口监听后的独立监听模式使用 `physical_only` 筛选物理网卡。只接收数字 IP 地址,不解析主机名;IPv6 规范化为压缩形式,链路本地地址保留本机 scope ID。
- 管理器约每 2 秒刷新接口快照。默认使用单个 `::` 双栈监听(关闭 `IPV6_V6ONLY`),IPv6 不可用时退回 `0.0.0.0`,界面可选择包括 VPN 在内的实际 IPv4/IPv6 分享;在“设置 → 常规 → 网络连接”中关闭“监听所有 IP 接口”并保存后,改为按物理网卡地址分别监听,界面也只列出这些地址。通配地址只用于绑定,不作为连接目标或 ICE 候选。
- 每个 IP 首次配置优先尝试 `52888`;无法绑定时请求操作系统分配端口,并在保持 socket 占用期间保存实际端口,避免“探测后释放、使用时被抢占”的竞争。
- 已保存的端口冲突时报告错误并重试原端口,不自动换号。手动改端口按“绑定新端口 → 保存配置 → 替换监听”的顺序执行,失败时保留原设置。
- “切换 IP”只改变显示和分享的地址。端口修改会停止旧端口接受新连接,但保留已经完成认证的会话及其他 IP 的监听。
- 网卡下线或 DHCP 换址导致本地 IP 从接口快照中消失时,管理器停止该地址的监听,并以 `InterfaceRemoved` 关闭使用该本地 IP 的已有会话。其他 IP 的监听继续运行;当前直连协议只允许一轮 ICE 协商,不支持会话内 ICE 重启或无缝迁移到另一张网卡,需要重新建立连接。
- 端口配置以 **IP 地址**为键,不跟随网卡标识。所有接口模式的端口单独保存在 `ports["0.0.0.0"]`,`listen_all` 保存模式,缺省为 `true`,已保存的 `false` 保留;设置页维护草稿,保存后应用,取消不改监听。切换不会覆盖各实际 IP 的端口。独立监听模式下,DHCP 分配未记录的新 IP 时,该地址重新执行“尝试 `52888` → 必要时分配可用端口”的首次配置流程;旧 IP 再次出现时沿用其保存的端口。
- 端口配置以 **IP 地址**为键,不跟随网卡标识。所有接口模式的端口单独保存在 `ports["::"]`,升级时从旧 `ports["0.0.0.0"]` 继承,`listen_all` 保存模式,缺省为 `true`,已保存的 `false` 保留;设置页维护草稿,保存后应用,取消不改监听。切换不会覆盖各实际 IP 的端口。独立监听模式下,DHCP 分配未记录的新 IP 时,该地址重新执行“尝试 `52888` → 必要时分配可用端口”的首次配置流程;旧 IP 再次出现时沿用其保存的端口。
切换监听范围时先停止旧 acceptor,绑定目标监听并原子保存模式与新端口;失败时保留原设置并尝试重新绑定旧端口,恢复失败由后台维护继续重试并显示错误。已认证通道保留,未完成认证的通道关闭。所有接口模式在网卡增减时保持同一个监听,已有会话仍按其实际本地地址的存续状态处理。
这里的 LAN 策略限定候选地址,不通过“是否有外网”判断工作模式,也不只是检查地址是否属于某个私网网段。实际连接仍要求本次 WSS 连接使用的双方 IPv4 的 TCP 和 UDP 路径可达。
这里的 LAN 策略限定候选地址,不通过“是否有外网”判断工作模式,也不只是检查地址是否属于某个私网网段。实际连接仍要求本次 WSS 连接使用的双方 IPv4/IPv6 的 TCP 和 UDP 路径可达。
IPv6 端点以 `[地址%本机作用域]:端口` 显示和保存到分享文本,裸 IPv6 使用默认端口;近期记录分开保存规范化地址与端口。通配监听接收的 IPv4-mapped socket 地址还原成普通 IPv4;用户输入不接受 mapped、回环、未指定或组播地址。逐地址 IPv6 监听开启 `IPV6_V6ONLY`,避免与 IPv4 监听重叠。
### 3.3 认证与协商时序
@@ -158,9 +160,11 @@ sequenceDiagram
IP 直连由**控制端**发起 offer。双方内部 ID 和随机 `session_id` 用于本次会话路由,不需要用户填写设备 ID。协商消息带会话 ID 和严格递增序号;会话管理器检查状态顺序,再调用 `ReceiveDirectSignal()`。MiniRTC 进一步检查 SDP、候选数量、地址和 ICE 标识一致性,并去重候选。
SessionManager 将当前 WSS socket 的本地与对端 IPv4 传入 `InitDirectPeer()`,由 MiniRTC 构造 `HostOnly` 策略,启动独立 ICE worker,不执行云端登录。MiniRTC 通过 `SignalSender` 回调把协商消息交给 SessionManager,再由 WSS 发送。
SessionManager 将当前 WSS socket 的本地与对端 IPv4/IPv6 传入 `InitDirectPeer()`,由 MiniRTC 构造 `HostOnly` 策略,启动独立 ICE worker,不执行云端登录。MiniRTC 通过 `SignalSender` 回调把协商消息交给 SessionManager,再由 WSS 发送。
MiniRTC 关闭 STUN/TURN 服务器访问、UPnP、ICE TCP 和公网打洞扩展,仅收集指定本机 IPv4 的 UDP host 候选。libnice 的 `direct-peer-address` 补丁同时检查远端候选和入站来源:在解析 STUN 连通性检查、学习新候选或回复数据之前,拒绝不匹配的 IP。ICE 自身的 STUN 检查报文仍会在双方之间使用,这与访问 STUN 服务器是两回事。
MiniRTC 关闭 STUN/TURN 服务器访问、UPnP、ICE TCP 和公网打洞扩展,仅收集指定本机 IPv4/IPv6 的 UDP host 候选。libnice 的 `direct-peer-address` 补丁同时检查远端候选和入站来源:在解析 STUN 连通性检查、学习新候选或回复数据之前,拒绝不匹配的 IP。ICE 自身的 STUN 检查报文仍会在双方之间使用,这与访问 STUN 服务器是两回事。
IPv6 候选按地址字节比较,允许等价压缩写法;线上候选不携带网卡作用域。链路本地远端候选由 libnice 绑定到当前 WSS 连接确认的本机作用域,接收路径在学习候选前同时检查地址与作用域,防止跨网卡匹配。新补丁提供 `direct-peer-ipv6` 能力标志并更新包配置,旧 libnice 不会静默接受 IPv6 策略。认证能力列表增加 `host-only-ipv6`,IPv4 连接仍仅要求原有能力以兼容旧客户端。
WSS 在连接成功后继续承担心跳和会话关闭消息。当前心跳约每 5 秒发送一次,等待响应超过 15 秒时关闭通道;WSS 关闭会驱动对应的 MiniRTC 会话清理。
@@ -262,7 +266,7 @@ LAN 近期记录的设备 ID 用于归档,不用于确认对端身份。重连
## 7. 支持范围与排查入口
IP 直连目前面向 Windows、macOS、Linux 的普通桌面客户端。浏览器、原生移动端、Windows 登录前服务和 Linux 无头控制台没有这条直连入口。数字 IPv4 和 UDP host 候选是当前实现约束。所有接口模式可接受 VPN 或虚拟网卡上的连接,控制端也允许通过这些接口发起连接,但仍要求双方 TCP、UDP 路径可达;不提供主机名解析、IPv6、回环远控或公网 NAT 穿透。
IP 直连目前面向 Windows、macOS、Linux 的普通桌面客户端。浏览器、原生移动端、Windows 登录前服务和 Linux 无头控制台没有这条直连入口。数字 IPv4/IPv6 和 UDP host 候选是当前实现约束。所有接口模式可接受 VPN 或虚拟网卡上的连接,控制端也允许通过这些接口发起连接,但仍要求双方 TCP、UDP 路径可达;不提供主机名解析、回环远控或公网 NAT 穿透。
| 现象 | 首先检查的层 |
| --- | --- |