Commit Graph
755 Commits
Author SHA1 Message Date
lbl 298c3965e2 chore: bump version to 2.0.2 v2.0.2 2026-08-23 11:37:34 +08:00
lbl 619a5bcf18 feat(web): share network configs via QR code 2026-08-23 11:03:28 +08:00
lbl 1acb5e8a86 feat(jni): expose detailed peer metrics 2026-08-23 10:28:37 +08:00
lbl e216416e21 fix(build): fall back to system protoc v2.0.1 2026-08-22 23:16:41 +08:00
lbl 7675a6868c fix(ci): restore FreeBSD and MIPS builds 2026-08-22 22:52:27 +08:00
lbl 70e584b37b chore(release): bump version to 2.0.1 2026-08-22 22:43:31 +08:00
lbl af4b5d9657 refactor(jni): remove legacy no_tun config field
fix(ui): clarify mapping exit description
2026-08-22 22:38:38 +08:00
lbl de0183139d feat: add TAP device mode and Ethernet interoperability 2026-08-22 21:47:49 +08:00
lbl 26a44e64bd build(web): build frontend UI automatically in build.rs
vnt-web/build.rs now runs the pnpm UI build when static/ is missing or
older than ui/src, so the embedded assets no longer need to be committed.
Set VNT_WEB_SKIP_UI_BUILD=1 to skip (a placeholder page is written).
Remove vnt-web/static from version control.
2026-08-22 04:12:46 +08:00
lbl 453234bf8d style: rustfmt tun general.rs 2026-08-22 04:05:10 +08:00
lbl 3e2274dfe5 fix(ui): simplify encryption password hint in config editor 2026-08-22 04:05:10 +08:00
lbl 53e092b63d refactor(ui): merge instances page into network dashboard
- Remove the redundant Instances page; its network detail and server
  connection sections now live in DashboardView below selectable
  instance cards
- Redirect legacy /instances and /general routes to /
- Add per-page subtitles to the header instead of a fixed caption
- Widen content container to 1700px for large screens
- Highlight online device count in green on the stats cards
2026-08-22 04:04:30 +08:00
lbl b7151880c0 feat(android): add JNI build and VPN support 2026-08-22 21:55:00 +08:00
lbl 60a4403f0e feat: add desktop updater and release automation 2026-08-22 10:00:24 +08:00
lbl da72670154 chore: update rand and resolve clippy warnings 2026-08-22 08:57:50 +08:00
lbl 2995cfcb26 fix(ui): refine web access and peer list 2026-08-22 08:52:25 +08:00
lbl 74c47bce69 feat: add outbound interface binding 2026-08-22 07:58:18 +08:00
lbl 55d47c5a1c feat: replace native select controls 2026-08-22 04:23:35 +08:00
lbl cd5b4431dc feat: add unified Tauri desktop client and secure web access 2026-08-22 04:09:44 +08:00
lbl 0ccd045e78 feat(vnt-web): 新增 /api/version,顶栏版本号不再依赖组网状态 2026-08-22 01:38:57 +08:00
lbl 39a4e60d39 feat(vnt-web): 配置变化提示与停止交互优化
- /api/info 新增 config_changed:与启动时的配置快照对比,启动后配置被修改(或文件缺失/解析失败)时实例卡片显示「配置发生变化」徽章,重启后自动消失
- 停止操作增加「停止中」中间态:徽章脉冲提示、按钮区禁用,实例真正停止后卡片渐隐消失,失败时自动恢复
- 停止/重启/移除确认弹窗优先显示配置名称,兜底文件名
2026-08-22 01:27:39 +08:00
lbl 6029fd113d build(vnt-core): 使用 vendored protoc,不再依赖系统安装 2026-08-22 01:27:28 +08:00
lbl 7513465b61 refactor(vnt-web): 前端重构为 Vite+Vue3+Pinia 工程并重新设计 UI
- 单文件 index.html 拆分为 vnt-web/ui(pnpm+Vite+Vue3 SFC+Pinia+Tailwind v4),构建产物输出到 static/
- UI 重新设计:浅色简洁风(可切换深色模式)、顶部导航、新增总览仪表盘首页
- 自定义弹窗/toast/确认框替换全部原生 alert/confirm,交互与过渡统一
- 响应式布局:移动端汉堡导航,表格与卡片自适应
- 实例卡片移除日志按钮;启动组网默认选中第一个配置
2026-08-22 00:53:51 +08:00
lbl 292d54fbc3 feat(vnt-web): 支持同时启动多个组网配置
- 后端实例状态从全局单槽改为按配置文件名隔离的 HashMap,每个实例持有
  独立的状态机、启动日志与任务组管理器
- 启动/停止/重启/信息/对端/路由/启动状态接口均按 file_name 维度隔离,
  新增 GET /api/instances 实例列表与 DELETE /api/instance 移除已停止实例
- 启动前冲突检测:device_id 相同(同服务器同组网范围内)或 tunnel_port
  显式相同则拒绝启动
- vnt_current_config.txt 改为每行一个配置名,重启后自动恢复所有运行中实例
- 前端重做为多实例模型:实例卡片列表、按实例隔离的启动日志弹窗、
  peers/routes 顶部实例切换器、配置列表运行状态标记、已停止实例可移除
2026-08-21 21:31:48 +08:00
lbl 94d380f9b6 update .gitignore 2026-08-21 19:33:18 +08:00
lbl 061135fb95 fix(vnt-web): save_config 强制 .toml 后缀
问题:list_configs 只列出 *.toml 文件(service_http.rs:812),而
save_config 对文件名不做后缀校验,可保存出列表中不可见、无法通过
Web 界面再次选择的配置文件。

修复:新增 normalize_config_file_name——无扩展名时自动补 .toml,已是
.toml 保持不变,其他扩展名直接拒绝并返回错误提示。

测试:新增 test_normalize_config_file_name 覆盖补后缀/保持不变/拒绝
.txt/.json 三种情况,cargo test -p vnt-web 4 个测试全部通过。
2026-08-21 03:11:14 +08:00
lbl 042254e5ac fix(cli): wintun.dll 不再只按存在性判断
此前仅当 wintun.dll 不存在时才释放内嵌版本,损坏或被替换的旧版
dll 会永久残留,后续创建 TUN 设备时才以难以理解的方式失败。

改为与内嵌版本做内容比较,不一致(不存在/损坏/旧版)即重写。

测试:缺失时写入、损坏时重写、一致时跳过。
2026-08-21 03:05:48 +08:00
lbl 45cce75085 fix(ipc): PORT 文件锚定 exe 目录且不再被多实例覆盖,读写加超时
问题:
1. PORT 文件用相对路径,读写位置随 CWD 变化。
2. 所有实例(显式端口、端口冲突退避到随机端口的)都写同一个
   PORT 文件,多实例互相覆盖,客户端可能连错实例;写入失败还会
   杀死整个 IPC 服务。
3. 客户端读响应、服务端读请求都没有超时,对端异常时永久挂起。

修复:
- PORT 文件锚定 current_exe 目录。
- 仅默认端口实例写 PORT 文件,写入失败降级为告警。
- 服务端读请求超时 10s,客户端读响应超时 5s。

测试:test_port_file_path_is_absolute 验证路径为绝对路径。
2026-08-21 03:02:22 +08:00
lbl c5403b6e31 fix(cli): 纯参数模式不再静默丢弃 --tunnel-port
build_from_args_only 用 ..Default::default() 填充其余字段,
args.tunnel_port 被丢弃,纯命令行参数模式下隧道端口设置无效。

顺带修复 vnt-ipc feature 下 main_cli.rs 的 clippy never_loop
(此前验证未覆盖该 feature):loop 两臂都直接 break,去掉 loop。

测试:test_args_only_keeps_tunnel_port 验证参数透传到 Config。
2026-08-21 02:55:08 +08:00
lbl e7b985c233 fix(utils): fallback 设备 ID 文件锚定可执行文件目录
machine_uid 获取失败时的 fallback 使用相对路径 "device_id",
读写位置随进程 CWD 变化,换个目录启动就会生成新 UUID,
设备 ID 漂移。

改为锚定 current_exe 所在目录(取不到时退回原相对路径行为)。

测试:test_fallback_id_path_is_absolute 验证路径为绝对路径。
2026-08-21 02:47:53 +08:00
lbl cc80bd2d37 fix(dns): 非法域名不再导致 panic
排查发现比报告更深一层的问题:dns-parser 0.8 的 add_question 对
超长 label 直接 assert panic(builder.build() 的 unwrap 只是第二层),
配置中的非法域名(如 label 超 63 字节)会使整个进程 panic。

修复:
- query() 调用前先经 is_valid_domain 校验(label 非空 ≤63 字节、
  全长 ≤253 字节,允许 FQDN 尾点),非法域名返回 InvalidInput。
- build().unwrap() 同步改为 map_err 作为纵深防御。

测试:非法域名 query 返回 InvalidInput 而非 panic;
is_valid_domain 合法/非法样例。
2026-08-21 02:45:46 +08:00
lbl 90a00215ca fix(port_mapping): UDP 映射 dest_map 不再无界增长,recv 出错不再杀任务
问题:
1. 每个新 src 创建映射条目与转发任务,任务虽有 60s 空闲超时退出,
   但映射条目从不移除,dest_map 随不同 src 数量无界增长。
2. recv_from 单次出错经 ? 传播直接杀死整个端口映射接收任务。

修复:
- 转发任务退出时经 remove_if_same(same_channel 身份比较)回收
  映射条目,防止误删重建后的新条目。
- recv_from 错误改为 log + 100ms 休眠 + continue。

测试:test_remove_if_same 覆盖旧任务误删新条目的竞态。
2026-08-21 02:39:24 +08:00
lbl 55d4b874bd fix(fec): 越界 packet_index 不再污染 group,payload 长度防 u16 截断
问题:
1. group 尺寸已知(校验包已到)后,数据分支对任意 packet_index
   resize received_shards 并写入,越界索引会把 shard 列表撑出
   data_shards+parity_shards,decode_with_rs 长度校验必然失败,
   整个 group 报废、FEC 恢复能力丧失;校验包索引落在数据区
   (< data_shards)还会直接覆盖数据 shard。
2. 内部批量格式的长度字段为 u16,payload.len() as u16 静默截断。

修复:
- 数据包:group.data_shards 已知时 packet_index >= data_shards 直接丢弃。
- 校验包:packet_index < data_shards 直接丢弃;
  received_shards 已被撑大(尺寸未知期的越界包)时放弃该 group。
- 长度字段改用 u16::try_from 检查转换(编码器本就有 Payload too big
  前置检查,解码器补齐对称防御)。

测试:越界数据包不污染 group(重构正常产出恢复包)、
数据区校验包索引被拒绝且 group 保持可用。
2026-08-21 02:36:20 +08:00
lbl 9993f9c894 fix(quic): TCP 解析失败不再静默丢包
open_quic_client 分支下,非分片 TCP 包若 TcpPacket 解析失败,
原实现 return true 表示"已处理",实际什么都没做——包被静默丢弃,
与文件中非 quic_client 路径(解析失败落到 return false)语义不一致。

改为 return false,把包交还给调用方的其他转发路径。

注:outbound() 依赖真实 IpStack,无法低成本构造单测;clippy 通过。
2026-08-21 02:27:46 +08:00
lbl 8ecbfca939 fix(quic): 入站 channel 满时丢包而非阻塞整条接收循环
QuicDataInbound::send 此前用异步 send 等待 channel 容量,消费端
(QUIC endpoint 驱动)处理不过来时,256 容量的 channel 一满就
阻塞整条接收循环,所有对端的入站流量被头部阻塞。

改为 try_send:满则丢包并告警(IP 包语义下可接受),关闭才报错。

测试:test_send_drops_when_channel_full(满时不阻塞)、
test_send_errors_when_channel_closed(关闭后报错)。
2026-08-21 02:25:40 +08:00
lbl 4f41468725 fix(quic): 按流发送任务增加空闲老化,不再永久残留
问题:每个 (protocol, src, dest) 三元组创建一条 QUIC 单向流和发送
任务,映射条目永不移除、rx.recv() 永不结束(map 持有 tx),流、
任务、映射随目的地址数量无界增长。

修复:
- 发送循环改为 120s 空闲超时,超时退出并关闭流。
- 任务退出时经 remove_sender_if_same(tokio Sender::same_channel
  身份比较)回收映射条目,避免误删重建后的新条目。

测试:test_remove_sender_if_same 覆盖旧任务误删新条目的竞态。
2026-08-21 02:09:30 +08:00
lbl cb3d1ba14d fix(p2p): NAT 信息更新不再发布 0.0.0.0、不再因 unwrap 瘫痪
问题:
1. 网卡扫描得到的 local_ipv4s 被 vec![local_ipv4] 整体覆盖;
   local_ipv4() 探测失败且扫描为空时回退为 0.0.0.0 并发布出去。
2. udp/tcp socket manager 及 local_ports 的多处 unwrap 在管理器
   未就绪时直接 panic,永久杀死 NAT 刷新任务(30 分钟循环)。

修复:
- 抽出 select_local_ipv4:探测地址无效时回退到扫描结果首地址,
  主地址并入列表首位且不覆盖其余扫描结果;无任何可用地址时
  跳过本次发布。
- socket manager 未就绪/查询失败时记录日志并跳过本次更新
  (tcp 端口缺失时置 0),30 分钟后正常重试。

测试:select_local_ipv4 四种场景(探测有效/回退扫描/去重/全空)。
2026-08-21 02:00:30 +08:00
lbl ed6f61ea2a fix(p2p): 路由评分分母 u32 溢出
rtt > 约39s 时 rtt * (10000 + loss_v * 10) 超出 u32 范围,
debug 构建下直接 panic,release 下评分错误。

改用 u64 计算并钳制回 u32。

测试:test_get_channel_score_large_rtt_no_overflow 覆盖 u32::MAX rtt
与正常大小关系。
2026-08-21 01:55:02 +08:00
lbl d49b7e6651 fix(jni): 全部 18 个 JNI 导出函数加 catch_unwind 防护
问题:vnt-jni 的 extern "system" 导出函数没有任何 panic 防护,
core 内任何 panic(unwrap/索引越界/断言等)跨 FFI unwind 会直接
abort 宿主 JVM。

修复:新增 catch_jni_panic(catch_unwind + AssertUnwindSafe)与
jni_guard! 宏,包裹全部 18 个导出函数:panic 时提取 payload 消息、
向 JVM 抛 RuntimeException 并返回该类型的默认值(jboolean/jint 0、
jlong -1、指针 null_mut、void 直接返回)。业务逻辑与签名不变。

说明:排查中发现报告举例的 core/mod.rs:140 expect 在当前代码下
实际不可达(no_tun=true 时 internal_nat_inbound 必为 Some),
但 FFI panic 防护本身仍然必要。

测试:3 个 catch_jni_panic 单测(正常值透传、&str/String payload
消息提取);cargo clippy --workspace --all-targets -- -D warnings 通过。
2026-08-21 01:45:30 +08:00
lbl 1cc71c8d62 fix: TCP 双向转发半关闭时不再截断对端数据
问题:tcp_port_mapping::stream_copy 与 tcp_nat::stream_nat 用
tokio::select! 组合两个 io::copy,任一方向先结束就 drop 另一方向。
客户端半关闭(shutdown 写方向)后,服务端的响应数据会被静默截断。

修复:抽出 copy_bidirectional_split,用 tokio::io::join 合并分离的
读写半部后走 copy_bidirectional——任一方向 EOF 时对另一端 shutdown
并继续转发剩余方向,直到双向完成。

测试:test_half_close_no_truncation 用 duplex 流验证半关闭后响应
完整送达(旧实现此处读到空数据)。
2026-08-20 23:20:06 +08:00
lbl 92463499ef fix(utils): TaskGuard::drop 不再调用 tokio::task::id()
问题:TaskGuard::drop 中调用 tokio::task::id()。abort 路径下
future 的销毁若发生在非任务上下文,id() 直接 panic 刷日志;
若发生在调用方任务上下文,则拿到调用方的任务 id,把它从
任务组中误删,破坏任务组记账。

修复:spawn 时在任务上下文内获取自身 id 存入 TaskGuard,
Drop 只使用保存的 id。

测试:test_abort_task_keeps_caller_bookkeeping 验证在同组任务内
abort 其他任务后,调用方条目不被误删、wait_all_stopped 正常返回。
2026-08-20 23:15:02 +08:00
lbl 837ef29c15 fix(tun): TUN 读写任一方向结束即整体退出,消除半开状态
此前 in_tun_loop/out_tun_loop 是两个独立任务,任一方向出错后
另一方向继续运行:TUN 处于半开状态,一个方向的流量被静默吞掉,
无恢复也无上报。

修复:两个方向合并为单个任务并用 tokio::select! 组合,任一方向
结束(出错或设备关闭)即取消另一方向并记录日志。DeviceTask
相应合并为单 SubTask。

注:依赖真实 TUN 设备,未做端到端测试;全部既有单测通过。
2026-08-20 23:12:23 +08:00
lbl 7fbac61e75 fix(server): 动态 DNS 发现的 wss:// 地址正确映射为 Wss 协议
此前动态 DNS TXT 记录中的 wss:// 被错映射为 TlsTcp,导致动态发现
模式下 WSS 连接实际不可用(会对 wss 端口发起裸 TLS over TCP)。

将前缀解析抽为纯函数 parse_dynamic_txt,wss:// -> ProtocolType::Wss,
其余映射保持不变。

测试:test_parse_dynamic_txt 覆盖 wss/quic/udp/tcp/ws/无前缀六种输入。
2026-08-20 23:10:12 +08:00
lbl 2e77801adc fix(server): 单台服务器注册失败不再拖垮整个 P2P 栈
问题:data_handle_task_connected 的重连循环中,注册被拒绝
(ResponseMessage::Error)、返回非注册消息、或虚拟网络发生变化时
break 后会调用 task_group.stop(),把包括其他健康服务器连接在内的
整个任务组全部停掉。

修复:
- 注册被拒绝/返回错误消息:与连接失败一致,退避 5 秒后重试,
  仅影响本服务器的重连。
- 虚拟网络发生变化:只结束本服务器的数据任务,不再 stop 整个任务组。

注:重连循环依赖真实服务器连接,未做端到端测试;全部既有单测通过。
2026-08-20 23:08:16 +08:00
lbl 0a73bc3356 fix(nat): TCP/ICMP NAT 不再因单点瞬时错误永久瘫痪
问题:
- tcp_nat listen_task:accept() 或 local/peer_addr 查询失败、
  no_tun 下 network.ip() 未就绪(None),任一错误经 ? 传播直接
  杀掉整个监听任务,NAT 永久失效且无重启。
- icmp_nat task:任一侧 socket 收发或单包处理出错同样经 ? 传播
  终止整个任务。

修复:
- 循环内错误改为 log::warn + continue,并加 100ms 休眠避免持续性
  错误造成空转。
- no_tun 下 network.ip() 为 None 时跳过重写/丢弃该包,而非报错退出。

注:该修复为异步 I/O 循环的容错行为变更,依赖真实 socket,
未补充端到端测试;已有 nat 相关单测全部通过。
2026-08-20 23:05:08 +08:00
lbl 40e2396741 fix(nat): 修复 ICMP NAT 映射只增不减导致内存无界增长
每个 ping 包都会以 (对端地址, identifier, sequence) 为键新增一条
永久映射,运行时间越长内存占用越大。

修复:
- 收到 EchoReply 命中映射即移除该条目(一次 echo 交换完成)。
- 映射值携带创建时间,select 循环中加入 60s 周期的 GC 分支,
  清理超时(60s)未收到应答的残留条目。

测试:test_evict_expired 验证超时条目被清理、未超时条目保留。
2026-08-20 23:00:44 +08:00
lbl d29c9e3122 fix(nat): 修复 UDP NAT 老化回收失效与误删竞态
问题:
1. GC 仅从映射表删除过期条目,其反向转发任务仍阻塞在 socket.recv
   上,任务与 socket 永久残留。
2. 条目过期被 GC 删除并以新 socket 重建后,旧任务退出时无条件
   remove 会把新条目误删。

修复:
- NatEntry 持有反向转发任务的 SubTask 句柄,GC 回收条目时一并
  stop 任务,释放 socket。
- 任务退出时通过 Arc::ptr_eq 确认表中条目仍是本任务的 socket 才删除。

测试:
- test_remove_if_current_only_removes_same_socket: 旧 socket 不能删新条目
- test_entry_inbound_task_stoppable: 条目任务可被 GC 正常终止
2026-08-20 22:58:11 +08:00
lbl af42ff7fe6 refactor(crypto): nonce 与 AAD 职责分离
nonce 只承担唯一性职责:seq(随机起始计数器,现占满 4 字节,
每流约 43 亿包才回绕)+ src + dst;不再用 msg_type 覆盖 seq 低位。

AAD 承担认证职责:msg_type(byte0)/flags(byte2)/reserved(byte3),
即所有传输中不变且不参与 nonce 的头部字节;ttl(byte1) 中继会递减,
仍不认证。

线上协议头部布局不变,但 nonce 推导方式改变,与旧版本
(含 48ba12d)加密不互通。

新增测试 test_tampered_msg_type_rejected:篡改 msg_type 必须解密失败。
2026-08-20 22:54:38 +08:00
lbl 016165ff8e fix(crypto): flags/reserved 头部字节纳入 AAD 认证
chacha20_poly1305 加解密 AAD 为空,flags 字节(COMPRESSED/FEC/GATEWAY)
未经认证,中间人可翻转标志位造成不可检测的丢包/语义篡改。

AAD 取头部 byte2(flags)/byte3(reserved);ttl(byte1) 中继转发时会递减,
不纳入 AAD 以保持中继兼容。

注意:此改动与旧版本(AAD 为空)不兼容,新旧节点间无法互通加密包。

测试:
- test_tampered_flags_rejected: 翻转 flags 必须解密失败
- test_ttl_change_still_decrypts: ttl 递减不影响解密
2026-08-20 22:40:30 +08:00
lbl b78140dc2d 修复 FEC 提交的 clippy 问题
ParityData 重导出改为仅 test 可见;去掉冗余切片与无用 vec!
2026-08-20 22:34:17 +08:00