Skip to content

fix: 同一台机器同一网络只允许一个客户端注册(含 ICMP NAT 权限降级) - #218

Merged
vnt-dev merged 4 commits into
vnt-dev:mainfrom
btsd321:fix/single-instance-lock
Nov 2, 2026
Merged

vnt-dev merged 4 commits into
vnt-dev:mainfrom
btsd321:fix/single-instance-lock

Conversation

@btsd321

@btsd321 btsd321 commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

问题

device_id 来自机器标识(machine_uid),所以同一台机器上无论用哪个用户、哪个程序启动,派生出的 device_id 都相同。

vnt2_cli 与 VNT Desktop 内置内核(vnt2-web)同时加入同一网络时,服务端把后注册的连接视为「同一设备的新连接」,踢掉先注册的那条:

ERROR handle_connection error: Session has been replaced by another client instance

被踢的一方立刻重连,又把对方踢掉,形成无限循环。客户端侧表现为:

  • 控制连接每 4~6 秒 Error on data_handle_loop: EOF 重连一次
  • vnt2_ctrl info 在 Online / Offline 之间反复跳
  • 虚拟网内所有节点都 ping 不通,而对端本身完全正常(最容易误判的地方)
  • 两台设备各建出一张虚拟网卡,其中一张拿到 169.254.x(APIPA)

修复一:内核单实例锁

新增 vnt-core/src/utils/instance_lock.rs,以 device_id + network_code 为键加跨进程独占锁:

平台 机制 释放
Windows 命名互斥体 Global\vnt-<sha256 前 8 字节> 进程退出(内核回收)
Linux 抽象命名空间 Unix socket(sun_path[0] == 0) 进程退出,含崩溃
macOS 等 flock 锁文件(临时目录) 进程退出
Android / iOS 空实现(宿主本身单实例) —

设计要点:

  • 同进程内共享同一把锁,跨进程才互斥。同一进程本来就共享 client_instance_id,重复注册没有意义;一个内核管理多个不同网络(Desktop 多配置)时各用各的键,互不干扰。
  • 键用 SHA-256 而非 DefaultHasher:不同版本、不同程序(cli 与 Desktop 内核)必须算出同一个名字,标准库哈希值不保证稳定。
  • 「查进程内表 → 抢平台锁 → 登记」在同一临界区完成,否则并发线程会误判成外部占用。
  • NetworkManager 在创建实例时抢锁,锁作为字段随实例销毁释放。

修复二:抢不到锁时报告已有实例

被拒绝属于正常情况(用户重复启动),按成功退出(退出码 0):

  • vnt2_cli:先通过 ctrl IPC 查询已有客户端(服务器 / 虚拟 IP / 在线节点),失败再读 web_access.toml 调 Desktop 内核的 /api/instances,打印状态后退出。
  • vnt2_web:输出明确提示后退出。
$ vnt2_cli -n btsd321_net -s 101.37.187.208:29872
本机已有 VNT 客户端在为同一网络运行,无需重复启动。
(单实例锁:vnt-06121241cdc84c22)

顺带修复:ICMP NAT 的权限降级

不加这个改动,本 PR 的 Linux / macOS CI 会继续红(上游 main 现在就是红的,共 4 个用例):

called `Result::unwrap()` on an `Err` value: new Socket RAW ICMPV4 failed
    Operation not permitted (os error 1)

创建 raw socket 需要 CAP_NET_RAW(Linux)或管理员(Windows),GitHub 的 Linux/macOS runner 都没有。原实现直接 ? 上抛,让整个网络启动失败。

改为仅在权限不足时(PermissionDenied / EPERM / EACCES / ERROR_ACCESS_DENIED)记 warn 并跳过 ICMP NAT,其它错误照常上报。ICMP NAT 只影响经本机出口的 ping 转发,TCP/UDP 组网与虚拟网互通不依赖它,这也顺带修好了「普通用户跑 --device-mode no 起不来」的问题。

验证

  • vnt-core 全量测试 279 passed(含用同一 network_code 在单进程内模拟 4 节点的 loopback 测试)
  • 新增 7 个单实例锁用例:锁名稳定性与网络隔离、同进程共享、不同网络互不阻塞、释放后重新获取、平台层拒绝第二个持有者、并发获取共享同一句柄、LockHeld 穿透 context 后仍可 downcast;另有 4 个 ICMP 降级判定用例
  • cargo clippy --workspace --all-targets -- -D warnings 无警告
  • 端到端:同一网络启动两个 vnt2_cli,第二个被干净拦下、退出码 0;第一个退出后第二个可正常启动

已知限制

  • macOS 的 flock 分支无法本地验证(无 macOS 环境),依赖 CI 的 macOS job 覆盖编译与用例。
  • Desktop 的 UI 提示(发现已有 vnt2_cli 时引导用户接管)属于 vnt-desktop 层,留待后续单独处理;内核已返回结构化错误,UI 侧接住即可。

device_id 由机器标识派生,因此同一台机器上的 vnt2_cli 与 VNT Desktop
内置内核会得出相同的 device_id。两者同时加入同一网络时,服务端把后注册
的连接当成同一设备的新连接并踢掉先注册的那条,双方随即互相替换、无限
重连——表现为虚拟网内所有节点都 ping 不通(对端本身完全正常)。

- 新增 vnt-core/src/utils/instance_lock.rs:以 device_id + network_code
  为键的跨进程独占锁(Windows 命名互斥体 / Linux 抽象 Unix socket /
  macOS flock 锁文件)。同一进程内共享同一把锁(同一进程本来就共享
  client_instance_id),跨进程互斥,正是 cli 与 Desktop 内核的冲突场景。
- NetworkManager 创建实例时抢锁,锁随实例销毁释放,不同网络互不影响。
- 抢锁失败按已有实例在工作处理:vnt2_cli 先通过 ctrl IPC 查询已有客户端,
  失败再读 web_access.toml 调 Desktop 内核的 /api/instances,打印状态后
  以 0 退出;vnt2_web 输出明确提示而非错误栈。
@btsd321
btsd321 marked this pull request as draft October 30, 2026 06:23
创建 raw socket 需要 CAP_NET_RAW(Linux)或管理员(Windows)。非特权环境
(CI runner、容器、普通用户跑 no 模式)里 start_icmp_nat 会直接失败,进而
让整个网络启动失败,Linux/macOS 上 4 个既有测试因此一直红着。

改为仅在权限不足(PermissionDenied / EPERM / EACCES / ERROR_ACCESS_DENIED)
时记 warn 并跳过 ICMP NAT,其它错误照常上报,避免掩盖真实故障。ICMP NAT
只影响经本机出口的 ping 转发,TCP/UDP 组网与虚拟网互通不依赖它。
- acquire 的查表、抢平台锁、登记放进同一临界区:原实现两步分离,并发时
  后到的线程会查到空条目后再抢已被占用的平台锁,被误判成外部占用。
- 新增并发共享、不同网络隔离两条用例;并发用例要求各线程持锁到齐再释放,
  否则先返回的线程会 drop 掉锁,测不出竞态。
- macOS 锁文件写打开被拒(文件由同机其他用户创建)时退回只读,flock 只需
  可读 fd,避免因此把启动打死。
- 探测模块排除含 CR/LF 的令牌(防请求头注入),并限制响应体积。
- README 常见问题补充单实例说明。
@btsd321 btsd321 changed the title fix: 同一台机器同一网络只允许一个客户端注册 fix: 同一台机器同一网络只允许一个客户端注册(含 ICMP NAT 权限降级) Oct 30, 2026
@btsd321
btsd321 marked this pull request as ready for review October 30, 2026 06:29
@btsd321
btsd321 force-pushed the fix/single-instance-lock branch from 219771f to fbe836a Compare October 30, 2026 06:37
clippy 的 suspicious_open_options 要求 create 与 truncate 必须成对声明,
该分支只在 macOS 编译,Windows/Linux 的本地 clippy 覆盖不到,CI 的
macOS job 因此报错。锁语义与文件内容无关,明确不截断。
@vnt-dev
vnt-dev merged commit 1bd0846 into vnt-dev:main Nov 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants