v3.0.0
ShadowLAN 3.0.0
Range: v2.0.0 (2026-09-09) → v3.0.0 (2026-09-13).
Hook and relay must be deployed together. A 2.x peer is dropped at registration (NODE version check). Old hooks cannot parse a 3.0.0 ASSIGN.
Breaking
- Control protocol is PVER 3 (was 2). Registration is the only versioned frame; gating it gates streams too.
ASSIGNno longer carries a trailinglink_idbyte. Payload is11+8*n(vnode, net, bits, membership only).- UDP source identity is the sender socket’s bound vport — the port a real NIC would stamp. The v2 slot-mark (
link_id*256+slot) is gone from the wire. Apps that foldrecvfromsources into peer state and dial them now get a real, dialable port. STREQis exactly 10 bytes (sid + gport + opener vnode). Short frames are rejected (noovirt=0fallback).
Protocol and relay
connect()completes when a dest link claims the stream (STOKat claim = SYN/ACK).STJOINEDstarts the raw pipe. A bridge failure after that is a post-connect reset, which real TCP can do too.- Half-close:
T_STSHUTFINs one direction and leaves the reverse alive. Implicit (port-only) streams now record the winning joiner so a joineeSTSHUTactually reaches the opener. - Designated-host election is gone. Implicit dials fan out like LAN ARP; every live link is asked, only the process that listens claims.
NODE_F_HOSTis an ordering stamp for wclient--hostmigration, not an election. - Membership lists only nodes with a live link. A fully dark node keeps its virtual IP for reconnect (up to
NODE_TTL) but disappears from peer tables immediately. - Beacons are forwarded, never cached or replayed. Same-process control links get NIC-once delivery (no duplicate beacons that fold phantom peers).
- UDP-over-TCP links are addressable (
("tcp", writer)), so rooms behind unfriendly NAT can start a join instead of dropping P2P asno-udp-endpoint. - Implicit claims use a short preferential window (
IMPLICIT_GRACE_S): all claims collected, freshesthost_claimwins, the rest getBUSY. - Accepted tunnel sockets get the same TCP tuning as the hook (Nagle off, large windows on the SYN).
- Stream verdicts match a kernel: nobody claims → framed
NO_ROUTE(fast refuse); claim then vanish → transport EOF/reset, not a 10 s stall.
Hook — LAN fidelity
- Accept door. Accepted game sockets present the opener’s vnode on
accept,getpeername, andgetsockname. Local port is the listen vport, not an aliased kernel ephemeral. Learn matches the bridge ephemeral — never “first live hosted row” (that stole the opener vnode on same-box hairpins). - Same-box hairpin. A process dialing its own vnode uses this process’s alias real port, not the vport number (which another node on the same kernel may own).
- Recv gather.
recvreturns the full in-queue thatFIONREADreported, not only the first chunk. Short first-chunk pops left lobby parsers waiting on a length prefix forever. - Hosted UDP identity. Session
ovirtis the parsed vnode, not the first four ASCII bytes of"10.200.…". - Replacement atomicity. A new
STREQfor the same(peer, gport)retires the old hosted session before claiming (kernel: a new connection implies the old is dead). - Close / duplex.
shutdown(SHUT_RD/WR),close()flushes the out-queue, peer FIN half-closes instead of killing both pumps, FIN vs RST distinguished (recv0 vsECONNRESET).SO_ERRORandselect/pollconnect edges match the kernel (no vacuous writable while still connecting). - Exit drain. Queued stream bytes and the control-frame queue flush on process exit (
ExitProcess/exit/_exit), so parting frames land instead of peers waiting out an app timeout. - Hosted pump. Unwritten game bytes are held and retried; the pump no longer drops a remainder when the relay write would block (that silently corrupted streams and looked like app-side stalls).
- Event-driven IO. Control link and per-stream pumps wake on kernel readiness plus a self-wake pair. Claim round-trip is sub-millisecond locally; poll slices are idle backstops only.
- Bind ledger. Node-scoped shared registry (Windows
Local\mapping / Linuxshm_open+flock). Same-node binds collide like a real kernel; different nodes on one OS alias underneath and never see each other. Dead-owner slots are reclaimed.SO_REUSEADDRUDP sharing matches the kernel (both sides must set it).bind(0)underLAN_ONLYallocates from the node ephemeral space. - Last-error.
hk_bindhas a single exit that snapshotsWSAGetLastError/errnoaround logging, so apps no longer seeerr=2for a refused bind. - Windows ledger crash. Startup AV from
GetProcessTimeson a kernelbase out-param is gone (PEB create-time +GetExitCodeProcessliveness only). Registry init is content-based; the section create handle stays open for the process lifetime. - Own-vnode dials loop back correctly (compare against
g_node, not the vnode number). - sid seeding is 64-bit on both Windows ABIs (the old
unsigned longfold was a no-op on Win32/Win64). - Channel policy after the mark fix: same-box double-node runs behave like two machines (forward and reverse both bridge). The old co-host yield guard and any-proto bridge backstop are gone — they existed only to paper over the mark leak.
Hook — structure
- One translation unit per
hk_*.c.hk_coreholds wire ops, tables,DLOCK, and policy.lan_hook.cis the version stamp only. Linux ishk_linux.c; Windows ishk_winsock/hk_wicmp/hk_winnic/hk_wininst. - Compile defaults to
-j$(nproc); a parent jobserver or explicit-jstill wins. Objects rebuild from headers, not from every sibling.c. pack.shlists every artifact and refuses a Windows zip that containslan_hook.so(or a Linux tarball that contains the DLLs).
Client
- wclient stream pumps are strongly referenced (
WinClient._spawn) so the loop cannot GC a live pipe. - wclient parses
STREQwith the shared 10-byte decoder.
Tests and harness
make -C hook testruns every Linux suite in parallel (runtests.py -j, ports fromtestutil.free_port()).test-alladds Wine to the same pool.- New / extended guards:
test_fullduplex(close, half-close, gather),test_bindfidelity,test_aliasbridge(same-box hairpin, accept-door triple, forward channel),test_socket_doors,test_stfail_semantics(including implicitSTSHUT),test_burst_connect,test_exitdrain,test_treeid.
Packages
shadowlan-v3.0.0-linux-amd64.tar.gz—lan_hook.so+ Python + docsshadowlan-v3.0.0-windows-amd64.zip— both DLLs + injector + Python + docs