* refactor(ohos): split facade feature and kernel crates * feat(ohos): protect transport sockets individually * fix(ohos): keep local proxy subnets off tun * fix(ohos): expose valid config enum values * refactor(ohos): finalize reusable core boundary * test(ohos): verify split package contracts * fix(port-forward): support wildcard userspace listeners 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. * chore(ohos): refresh split workspace lockfile * fix(socket): normalize Windows raw socket handles * refactor(socket): carry VPN protection through host bind options * refactor(socket): simplify protection defaults and TUN ingress * refactor(socket): consolidate native protection and socket creation * fix(socket): protect outbound UDP paths * fix(socket): preserve VPN routing for RPC listeners --------- Co-authored-by: FrankHan <frankhan@FrankHans-Mac-mini.local> Co-authored-by: KKRainbow <443152178@qq.com>
5.0 KiB
Host socket protection
VPN bypass is a socket-creation requirement, not an operation on a socket that core has already connected. Core and WASI guests never need an OS file descriptor.
Portable requests
TcpBindOptions::need_protect and UdpBindOptions::need_protect travel through the
existing connect/bind operations. A host requiring VPN bypass must acknowledge
successful protection before connect, bind/listen, or publishing the socket.
Failure or cancellation fails creation and discards the owned socket; emitting
an event alone is not acknowledgement. Platforms without VPN bypass can treat
the requirement as a no-op.
- TCP connect constructors request protection, including egress for proxies.
- TCP transport/hole-punch listeners request protection. Hosts retain this flag on the listener and protect accepted children before handing them to core.
- Local ProxyNat/SOCKS/port-forward/port-lease listeners do not request it.
- UDP transport, candidates, listeners, NAT egress and STUN request protection; HolePunchControl, local SOCKS/port-forward and port-lease sockets do not.
- Low-level TCP/UDP bind options default to protection, including deserialization
of options without
need_protect. The named local/TUN-facing constructors setfalseexplicitly. The UDP default keeps its historical purpose label for socket setup; only the namedhole_punch_control()constructor opts out. with_bind()replaces the entire bind object. A local listener's replacement must retain its opt-out rather than inherit the protected default. A native adapter must honor explicitfalse, not silently change it based onpurpose.- DNS and source-route queries are already host-owned operations. A bypass-enabled host must protect their underlying sockets before querying/probing, including DNS TCP fallback, rather than letting system DNS silently bypass this contract.
TUN-facing ingress and port forwarding
The extra force_smoltcp wildcard port-forward ingress listener has been removed.
Normal TUN-backed ingress is delivered to the existing unprotected native listener;
its accepted sockets remain unprotected so overlay replies can return through TUN.
The physical/underlay egress socket is protected independently. A separate
DataPlane listener must not mask broken host/TUN routing in this path.
This does not remove the existing public DataPlane listener APIs, the generic
no-TUN smoltcp TCP proxy, or force_smoltcp itself. Those have other uses. Whether
Android subnet proxy works without forced smoltcp needs actual platform regression
testing; socket-creation unit tests alone do not establish that result.
Native integration and HarmonyOS
The native easytier adapter implements the creation requirement using an async
NativeSocketProtector callback. This is a native implementation detail, not a
new portable or WASI ABI. It takes only the native handle; the creation options
select policy, without a second purpose enum. Namespace switching is confined to synchronous socket
creation and never held across the callback's await.
The existing native bind builder is async and shared by TCP/UDP creation. Its
legacy direct-call default remains unprotected; portable factories explicitly
pass their core bind options (default protected). Callers must await .call().
TCP listeners reuse TCP socket creation then listen, instead of duplicating the
setup. Existing legacy WebSocket direct-call policy is unchanged.
The HarmonyOS broker wakes the already-pending request consumer with Notify
(no polling timer). It keeps a duplicate FD alive until ArkTS completes
VpnConnection.protect(fd) and returns its ACK. The waiting creation future is
woken immediately by the oneshot acknowledgement. Failure stays fail-closed;
shutdown retains dispatched FDs for late ACKs to prevent FD reuse races.
The ArkTS request shape is unchanged; its diagnostic purpose string is now
the generic "socket". Neither ACK routing nor protection policy uses that label.
This guarantees ordering, not a wall-clock real-time bound: OS/ArkTS scheduling can still delay protection. Such a delay keeps the socket unconnected; it must never allow the first SYN/query to race ahead of protection.
WASI option format
The easytier_host import names and function signatures are unchanged. TCP
connect, TCP listen and UDP bind use option document version 3 (previously 2):
one u8 boolean need_protect is inserted immediately after the existing purpose
byte and before the optional bind-device field. All other field encodings and
purpose values are unchanged. The document version lets older hosts reject
unsupported options instead of silently ignoring protection. DNS, environment,
instance-config and data-plane layouts/versions are unchanged.
An embedding host must update its versioned option decoder and honor the flag inside its existing creation implementation. The external host implementation is not in this repository: building the guest proves propagation/compatibility of imports, not that every external host has implemented platform protection.