Conversation
Keep the existing Host listener intact while adding a DataPlane listener for force-smoltcp IPv4 wildcard rules. Keep literal loopback destinations on the local Host path instead of exporting them through an exit node.
| Ok(()) | ||
| } | ||
|
|
||
| fn spawn_data_plane_tcp_port_forward( |
There was a problem hiding this comment.
重新沿收包路径核对了一遍:这段与 per-socket protect 是独立功能。我先保留并补了说明,但收窄原来的必要性描述。
native port-forward listener/accepted socket 现在保持 unprotected,远端 TCP 由 EasyTier 自己终止,HDC 只处理本地连接,因此不能再仅凭“HDC 是 UID0”就断言这段一定不能删。普通 no_tun=true 也已有 generic TCP proxy -> 127.0.0.1:同端口 -> native port-forward 的回退路径,不应说删除会一概破坏 no-tun。
这段额外提供的是 force_smoltcp=true + IPv4 wildcard 下的 userspace inbound endpoint:匹配的 overlay TCP 在 DataPlane 直接终止,回包经 peer plane 发出,不依赖 host TUN 注入和 native TCP 的返程路由。force_smoltcp 可以与 no_tun=false 同用,此时 generic TCP proxy 不一定接管本机虚拟 IP;删除会把这类入站重新交给 host TUN。
所以它不是 protect 的必要组成部分,但原综合 PR 还包含这个可选的用户态入站能力,本次没有仅凭推断删除。尚未做“新版 unprotected native forward 与该 userspace path”的 HarmonyOS A/B,不能宣称新策略下 HDC 仍必须走它;如果希望将这项独立能力从本 PR 拆开,我可以另拆。
There was a problem hiding this comment.
同意后续讨论的指正,已在 0353f99 移除这段额外 userspace ingress 及其调用;port_forward.rs 的最终内容已恢复到本 PR 基线。
正常 TUN 场景应让入站走现有 native listener,listener/accepted child 不保护,回包才能按 TUN 路由返回;物理 egress socket 单独保护。我之前用“独立用户态能力”解释保留,扩大了本次修复范围,现已收敛,不再靠额外 DataPlane listener 掩盖 host/TUN 路由问题。
原有 DataPlane bind/connect API、generic no-tun smoltcp proxy、force_smoltcp 选项本身不动;保留 literal loopback 走 Host 的独立路由修正。安卓子网代理是否恢复、哪些场景可不再 force_smoltcp,仍需真实平台回归,暂不将本地测试当作该结论。
本轮 Core 含 proxy-smoltcp-stack 的 888 项测试、native protect 时序 3 项、OHRS 7+5 项、N-API check 和 WASI check 均通过。
| let socket = create_dns_tcp_socket(&self.context, server_addr, bind_addr); | ||
| Box::pin(async move { | ||
| let socket = socket?; | ||
| protect_native_socket(&socket, NativeSocketPurpose::DnsTcp).await?; |
There was a problem hiding this comment.
看看能否把 protect 做到 create socket 接口里面,need_protect 放到 socket bind options 里。
单独加一个接口意味着所有 abi 都要改,放到 bind options 里的话,wasi 的版本也可以受益
There was a problem hiding this comment.
已按这个方向调整,提交 bb8fdb6:
need_protect放进 easytier-core 的TcpBindOptions/UdpBindOptions,由现有 connect/bind 创建请求带给宿主;Core/WASM 不需要获得真实 FD,也没有新增 protect import。- native 的保护回调只作为宿主实现细节,创建流程等待保护成功 ACK 后才 bind/connect/listen;accepted child 继承 listener 的 flag,交付给 Core 前等待保护完成。失败/取消不放行 socket。
- DNS TCP/UDP、route probe、UPnP probe 改为复用同一创建路径,移除了 DNS 这里独立的 post-create protect 调用;保护启用时也不会回退到不可控的 system DNS。
- HarmonyOS 沿用 Notify + oneshot 的请求/ACK 桥接,不靠定时轮询;ArkTS protect 完成才唤醒创建方,保留 duplicated FD / fail-closed 语义。不是 fire-and-forget 事件。
- WASI import 名字和签名不变,现有 socket options 文档升级 v2 -> v3,在 purpose 后增加一个 need_protect 字节。外部宿主需要更新 options decoder 并在自己的创建实现内执行保护;本仓库没有外部宿主实现,不能把 guest 编译通过说成外部平台已完成保护。
验证:easytier-core 744 tests;native protection ordering 3 tests;OHRS core/features 7+5 tests;N-API tests check;native default/no-default check;wasm32-wasip1 精简及 default+proxy-smoltcp-stack check 均通过。契约与 v3 布局写在 docs/socket-protection.md。新一轮 CI 已由 push 触发。
There was a problem hiding this comment.
已根据后续建议在 0353f99 将低层 TCP/UDP bind options 的 need_protect 默认统一为 true(Rust Default/new 与 JSON 缺省字段一致),删掉连接、打洞、STUN、DNS、route/UPnP probe 等调用点冗余的 with_need_protect(true)。
只在原有例外构造器显式 false:本地 ProxyNat/SOCKS/PortForward/PortLease TCP listener,以及 UDP HolePunchControl/SOCKS/PortForward/PortLease。特别检查了 with_bind 替换整个对象的地方,内核 TCP proxy listener 的重新构造 bind 也显式 false,避免默认值改变后误保护 TUN 回包。
WASI v3 布局和 import 签名不再变化,flag 仍由创建请求传递。原生 callback / HarmonyOS Notify+oneshot ACK 的时序不变:保护成功后才 bind/connect/listen,失败不放行。测试验证四种本地 TCP listener 及其 accepted socket 不进入保护回调、出站默认保护,以及 serde/WASI 保留显式 false。
移除额外 port-forward ingress 后,easytier-core 相对 PR 基线的修改文件从 20 减到 12。本轮验证:Core 888 passed,native 时序 3 passed,OHRS 7+5 passed,N-API check、wasm32-wasip1 default+proxy-smoltcp-stack check、native no-default check、fmt/diff check 通过。
There was a problem hiding this comment.
继续做了实质简化,提交 5aef4ea:
- 删除只剩诊断用途的 NativeSocketPurpose,native protector 回调现在只接收 handle;need_protect 仍是唯一保护决策输入。Unix/Windows 通过 socket2/SockRef 共用一个保护入口,保留 i686 handle -> u64 的正确转换。
- 合并旧 bind 和 bind_with_protection 为一个 async bind;仓库里仅一个 legacy WebSocket 调用加 await、一个同步测试改异步,不增加预创建socket包装层。portable options 默认 true 不变,factory 显式传 flag;底层 builder 保留 legacy 直调不保护的默认行为。
- TCP listener 复用 create_tcp_socket 再 listen(1024),去掉重复选项装配;UDP去掉一层纯转发wrapper;DNS继续复用共同创建路径,没有恢复重复的DNS-only实现。
- ACK时序、fail-closed、namespace guard 不跨 await、SO_MARK 在 protect 前应用、accepted child继承和duplicated FD生命周期都保留。ArkTS请求shape不变,purpose诊断字段仅收敛为通用 socket。
保留三个时序测试,并加强listener创建本身等待ACK的断言。native保护3、TCP2、WebSocket2、Core888、OHRS7+5通过;N-API/WASI/no-default/fmt检查通过。
按相同PR基线164e2db6统计:native diff 634 -> 558行(-76,12.0%),Core/native合计868 -> 792行(-8.8%);native实际源文件总行数净减138。文件数native8 -> 9,是合并bind所需的WebSocket单处await迁移,不是搬文件凑减幅。未更新设备,CI已由push触发。
| .bind_tcp( | ||
| TcpListenOptions::proxy_nat(listen_addr).with_bind( | ||
| TcpBindOptions::default() | ||
| .with_need_protect(false) |
There was a problem hiding this comment.
udp proxy 的 socket 也得 protect false 吧
| let overlay_destination = if local_virtual_destination { | ||
| true | ||
| } else if dst_ip.is_loopback() { | ||
| false |
There was a problem hiding this comment.
这里是多余的,loopback 地址进下面的 else 之后,必然返回 false
|
|
igd-next 的处理看起来会比较麻烦 |
|
igd 可以先不用管,把其他的修一下吧 |
|
按最新 socket review 更新并推送
关于 IGD: |
Sync with main after the igd-next crate was inlined (EasyTier#2555). Conflict resolution in easytier/src/common/upnp.rs: - drop the stale "type TokioGateway = Gateway<Tokio>" alias; the inlined crate::igd_next::Gateway is no longer generic over the async provider - keep new_protected_natpmp from this branch, which routes NAT-PMP sockets through the protected socket factory Everything else (lib.rs, wasi imports, lockfiles) auto-merged; cargo check -p easytier --lib --features upnp passes.
Disable socket protection at the native RPC listener entry point so accepted connections can return traffic through TUN. Keep the existing IPv6-only bind policy and the protected defaults for transport listeners. Exercise RPC bind and accept with a failing protector, and align UDP constructor, loopback control, and WASI encoding tests with the current default-protected policy and explicit local socket opt-outs.
Brings in 16 upstream commits (347 files) on top of the GUI redesign, including the core logic changes below. Core / runtime - fix(core): reset foreign networks when the event handler lags (EasyTier#2550) - fix(config): normalize [secure_mode] when loading TOML (EasyTier#2562) - feat(acl): ACL handling for JSON and TOML formats (EasyTier#2540) - feat(upnp): inline the IGD client (EasyTier#2555) - feat(wasi): run the core on Cloudflare Workers and browsers (EasyTier#2548) - feat(web): URL path support for listener addresses (EasyTier#1967) - feat(easytier-go): expose CreateInstanceTOML and ShowNodeInfo (EasyTier#2557) - refactor(ohos): split OHRS crates with per-socket VPN protection (EasyTier#2543) Android - fix(android): keep the VPN running when pressing back (EasyTier#2546) - feat(android): VPN quick-settings tile (EasyTier#2511) - fix(android): avoid duplicate foreground notifications (EasyTier#2559) Conflict resolutions (16 files) - index.vue / mobile_vpn.ts / mobile_vpn.test.ts: kept the rewritten phone shell and ported upstream's tile wiring onto it, adapting the PrimeVue toast.add(...) calls to this branch's own toast() helper. - Config.vue / UrlInput.vue: kept the Vuetify rewrites (the merged frontend-lib no longer depends on PrimeVue) and ported EasyTier#1967's path support and listener-protocol filtering into them by hand. - config-generator: adopted upstream wholesale (Magisk module WebUI), reverting this branch's Vuetify conversion of that standalone tool. - Android native: union. Kept the localized notification content, channel hygiene and the deliberate unused-permission cleanup; took upstream's specialUse service type (its API guard was wrong at >= Q), the back-press handler, the QS tile service and its alias. - Removed a duplicate PROPERTY_SPECIAL_USE_FGS_SUBTYPE that the merge produced on TauriVpnService, and a duplicated happy-dom override in pnpm-workspace.yaml; regenerated pnpm-lock.yaml. Verified after merge: frontend-lib vitest 17/17, easytier-gui mobile_vpn 20/20, frontend-lib build (vue-tsc -b + vite) and easytier-gui vue-tsc --noEmit + vite build all pass.
The ohos workflow runs `cargo test/check --locked` against easytier-contrib/easytier-ohrs/Cargo.toml, which failed with: error: cannot update the lock file .../easytier-ohrs/Cargo.lock because --locked was passed to prevent this The committed lockfile is byte-identical to upstream/main, i.e. upstream 2.7.0 ships it stale after the EasyTier#2543 OHRS crate split (186 entries for the pre-split layout). Regenerated with `cargo metadata`; `cargo metadata --locked` now succeeds both locally and in the runner's step. Only stale entries were dropped (4 insertions / 186 deletions); no dependency versions were bumped.
背景
根据与 EasyTier 作者关于 HarmonyOS VPN socket 管理的讨论,本 PR 不再对整个进程调用
protectProcessNet(),而是利用主线已经集中的 TCP/UDP 创建入口,对需要走物理网络的 socket 单独调用 HarmonyOSVpnConnection.protect(fd)。本 PR 同时完成 OHRS 包职责拆分,并吸收原 PR #2534 的本机子网代理路由、配置 Schema 和 loopback 路由修复,用于替代 #2534。根据后续 review,额外的通配 TCP 用户态入站旁路已移除,正常 TUN 入站使用现有 native listener。
修改内容
1. 拆分 OHRS 内层包
保留 ArkTS 只加载一个
easytier-ohrsHAR/.so,Rust 内部按职责拆成两个同级包:easytier-ohos-coreNativeInstanceManagereasytier-ohos-featureseasytier-ohrscore与features互不依赖,基础运行包不会再反向拉入 SQLite、配置迁移或 Schema UI 依赖。2. 按 socket 精细保护 VPN 外部流量
保护意图位于
easytier-core的TcpBindOptions::need_protect/UdpBindOptions::need_protect,通过现有创建请求交给宿主。native adapter 使用异步NativeSocketProtector回调,等待成功 ACK 后才 bind/connect/listen 或暴露 accepted child;未注册 capability 的平台保持 no-op。WASI 不新增 import,现有 socket options 升级到 v3 并携带该布尔值;外部宿主需更新 decoder 并在自己的创建流程内落实保护。详见docs/socket-protection.md。当前覆盖:
HarmonyOS OHRS core 实现异步 request/ack broker:
OwnedFdVpnConnection.protect(fd)protect/create/destroy通过同一个异步队列串行化,避免 TUN 重建与 FD protect 交叉低层 TCP/UDP bind options 的 need_protect 默认 true,Rust Default/new 与 JSON 缺省字段一致;删除各出站调用点冗余的 true 设置。native/OHRS 不按 purpose 重复推断、覆盖显式 flag;下列原有例外构造器显式 false:
PortForward/ProxyNat/Socks5等客户端侧 TCP listenerHolePunchControlSocks5association(当前 purpose 同时表示 ingress/egress,先避免错误批量保护)PortForward/PortLease配套 ArkTS 应用已移除
protectProcessNet(),在存在 TUN 的整个 runtime 生命周期中运行 pull pump,并复用同一个VpnConnection完成单 FD protect 与后续 TUN create/destroy。配套实现分支:FrankHan052176/EasyTier-ArkTS@refactor/per-socket-vpn-protection。3. 收敛为正常 TUN/native 端口转发路径
spawn_data_plane_tcp_port_forward及force_smoltcp + IPv4 wildcard调用分支;port_forward.rs相对 PR 基线已无差异。force_smoltcp选项;本 PR 不全面删除 smoltcp。4. 修复本机子网代理 TUN 路由
本机配置的
proxy_cidrs只作为网络宣告,不再加入同一节点的 HarmonyOS TUN routes。本机 TUN 继续包含:routesproxy_cidrs避免 Core TCP/UDP proxy 访问物理局域网目标时被自己的 TUN 再次捕获。
5. 修复配置 Schema 枚举
CompressionAlgoPb::None1后被NetworkConfigJSON 反序列化拒绝6. CI 契约检查
OHOS workflow 现在会串行执行两个内层包的测试,并使用
cargo check --tests验证外层 N-API facade 的测试代码可编译。验证
最新简化:5aef4ea2
先前默认值与TUN收敛:0353f993
easytier-core --lib --features proxy-smoltcp-stack:888 tests passed,包含 DataPlane/port-forward 原有回归。cargo check --tests通过。wasm32-wasip1default + proxy-smoltcp-stack、native no-default check、root/nested fmt 与 diff check 通过。先前 Review follow-up:bb8fdb6e
easytier-core --lib:744 tests passed。cargo check --tests通过。wasm32-wasip1精简及 default + proxy-smoltcp-stack check 通过;WASI import 签名不变,options decoder 需支持 v3。先前构建基线
已完成:
cargo check -p easytier --libcargo test -p easytier-ohos-core -p easytier-ohos-features --lib -- --test-threads=1cargo check -p easytier-ohrs --testslibeasytier_ohrs.so和easytier-ohrsArkTS record待补真机网络回归:
0.0.0.0:15555 -> 127.0.0.1:HDC提交结构
Closes and supersedes #2534.