@push.rocks/smartvpn 1.21.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/assets/third-party-licenses.md +157 -0
  2. package/dist_rust/{smartvpn_daemon_linux_amd64 → smartvpn_daemon_linux_amd64_musl} +0 -0
  3. package/dist_rust/smartvpn_daemon_linux_amd64_musl.tsrust-build.json +14 -0
  4. package/dist_rust/{smartvpn_daemon_linux_arm64 → smartvpn_daemon_linux_arm64_musl} +0 -0
  5. package/dist_rust/smartvpn_daemon_linux_arm64_musl.tsrust-build.json +14 -0
  6. package/dist_ts/00_commitinfo_data.js +2 -2
  7. package/dist_ts/smartvpn.classes.vpnbridge.d.ts +3 -3
  8. package/dist_ts/smartvpn.classes.vpnbridge.js +13 -45
  9. package/dist_ts/smartvpn.classes.vpnclient.d.ts +5 -4
  10. package/dist_ts/smartvpn.classes.vpnclient.js +9 -5
  11. package/dist_ts/smartvpn.classes.vpnconfig.d.ts +1 -0
  12. package/dist_ts/smartvpn.classes.vpnconfig.js +52 -14
  13. package/dist_ts/smartvpn.classes.vpnserver.d.ts +17 -4
  14. package/dist_ts/smartvpn.classes.vpnserver.js +80 -40
  15. package/dist_ts/smartvpn.interfaces.d.ts +151 -16
  16. package/dist_ts/smartvpn.paths.d.ts +2 -0
  17. package/dist_ts/smartvpn.paths.js +14 -1
  18. package/notices/aho-corasick-license.txt +21 -0
  19. package/notices/cargo-dependencies.html +5202 -0
  20. package/notices/compiler-builtins-license.txt +275 -0
  21. package/notices/defmt-license.txt +25 -0
  22. package/notices/inventory.json +1427 -0
  23. package/notices/llvm-libunwind-license.txt +311 -0
  24. package/notices/mit-source-attributions.txt +31 -0
  25. package/notices/musl-copyright.txt +193 -0
  26. package/notices/proc-macro-error2-license.txt +21 -0
  27. package/notices/rust-standard-library.html +8266 -0
  28. package/notices/valuable-license.txt +25 -0
  29. package/package.json +8 -6
  30. package/readme.hints.md +69 -3
  31. package/readme.md +313 -5
  32. package/third-party-notices.md +81 -0
  33. package/ts/00_commitinfo_data.ts +1 -1
  34. package/ts/smartvpn.classes.vpnbridge.ts +12 -50
  35. package/ts/smartvpn.classes.vpnclient.ts +9 -5
  36. package/ts/smartvpn.classes.vpnconfig.ts +49 -13
  37. package/ts/smartvpn.classes.vpnserver.ts +94 -40
  38. package/ts/smartvpn.interfaces.ts +155 -16
  39. package/ts/smartvpn.paths.ts +19 -0
@@ -0,0 +1,25 @@
1
+ Copyright (c) 2021 Valuable Contributors
2
+
3
+ Permission is hereby granted, free of charge, to any
4
+ person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the
6
+ Software without restriction, including without
7
+ limitation the rights to use, copy, modify, merge,
8
+ publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software
10
+ is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice
14
+ shall be included in all copies or substantial portions
15
+ of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19
+ TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20
+ PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21
+ SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24
+ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25
+ DEALINGS IN THE SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartvpn",
3
- "version": "1.21.0",
3
+ "version": "2.0.0",
4
4
  "private": false,
5
5
  "description": "A VPN solution with TypeScript control plane and Rust data plane daemon",
6
6
  "type": "module",
@@ -25,20 +25,22 @@
25
25
  "dependencies": {
26
26
  "@push.rocks/smartnftables": "1.2.0",
27
27
  "@push.rocks/smartpath": "^6.0.0",
28
- "@push.rocks/smartrust": "^1.4.0"
28
+ "@push.rocks/smartrust": "^2.0.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@git.zone/tsbuild": "^4.4.2",
32
- "@git.zone/tsrun": "^2.0.4",
33
- "@git.zone/tsrust": "^1.4.1",
34
- "@git.zone/tstest": "^3.6.6",
35
- "@types/node": "^25.9.1"
32
+ "@git.zone/tsrun": "^2.0.6",
33
+ "@git.zone/tsrust": "^1.11.0",
34
+ "@git.zone/tstest": "^4.0.0",
35
+ "@types/node": "^26.4.1"
36
36
  },
37
37
  "files": [
38
38
  "ts/**/*",
39
39
  "dist_ts/**/*",
40
40
  "dist_rust/**/*",
41
41
  "assets/**/*",
42
+ "notices/**/*",
43
+ "third-party-notices.md",
42
44
  "readme.md",
43
45
  "license.md"
44
46
  ],
package/readme.hints.md CHANGED
@@ -1,8 +1,74 @@
1
1
  # smartvpn hints
2
2
 
3
+ ## Musl distribution and qualification checkpoint (2026-09-07)
4
+
5
+ - Rust 1.95.0 produces locked, remapped Linux amd64/arm64 musl artifacts. Both
6
+ passed ELF architecture/static-link/no-glibc checks. ARM64 management startup
7
+ and confirmed termination passed under QEMU, not privileged ARM64 hardware.
8
+ - The exact amd64 SHA-256 `9dae4722a783f8de4ce2c5066eff1bf5d6ebde48750aaf3e96ad4e0374e153a3`
9
+ passed the dedicated serve.zone/testing KVM scenario: real WS/Noise + Linux
10
+ managed TUN split routes, directed allow/deny, reconnect, natural revocation,
11
+ partial route-conflict rollback, empty restart and child termination. Cleanup
12
+ joined, removed owned namespaces, and preserved VM DNS and default routes.
13
+ The VM is shut off. WSS/private CA, IPv6 workload traffic, privileged ARM64,
14
+ standalone kernel modes and Pallet/Cloudly production integration remain unqualified.
15
+ - Verification passed 109 TypeScript tests, all source/test type checks, 255 musl
16
+ Rust unit tests, three Rust integrations, and the separate 181-second WireGuard
17
+ expiry test. Build/test invocations must remain sequential because tsrust
18
+ replaces dist_rust. Cargo tests using their separate target tree may run alongside.
19
+ - Release tooling was committed separately. The complete Cargo/compiler/runtime
20
+ notice index is `third-party-notices.md`, with hashed inputs and all 278 locked
21
+ registry packages in `notices/inventory.json`. Tests enforce coverage and payload
22
+ inclusion. Redistribution of the unchanged tun 0.7.22/WTFPL dependency was
23
+ explicitly approved on 2026-09-07; this is not a production deployment approval.
24
+
25
+ The authority notes below retain earlier development checkpoints. Qualification
26
+ claims above supersede their historical managed-TUN test gaps, not the remaining
27
+ standalone-kernel or production-integration limitations.
28
+
29
+ ## Authority prerequisites (2026-09-07)
30
+
31
+ - The Rust managed_network compiler validates complete schema-v1 declarations before active effects. Managed ClientRegistry retains the lifetime binding, applied snapshot and exact pending candidate/pool/affected set. Admissible stamps quarantine affected nodes under the native/WG admission gate; raw entry/stamp reads are not admission. Server-owned serialized reconcile joins cancelled sessions outside the gate. With WG, the loop prepares replacements then swaps peer crypto, candidate registry and exact pool synchronously under the registry gate; failures retain quarantine and only exact-target retry. Without WG the same commit runs in the tracked apply owner. Equal canonical replay preserves sessions; per-node effective authority includes directed grants, not only route unions.
32
+ - Public reconcileManagedNetwork/getManagedNetworkStatus/getManagedNodeProjection IPC is typed on both sides. Managed mode has no hub host-network effects and no standalone writer. Unix apply releases the outer VpnServer mutex before awaiting; status from a second connection remains available, and dropping a requester does not cancel the tracked operation. Regressions cover actual native/WG workload relay and MTUs, denied domains, unchanged owners, failed preparation/retry, direct raw-WG writer rejection, address/key reuse, disconnected IPC and whole-server drain.
33
+ - Native managed assignment validates bounded canonical owned/remote prefixes and reports live authority/lifetime/revision through client status. Native Linux managed_tunnel uses the published rtnetlink API, exclusive ACKed route adds and a kernel-allocated nonpersistent /32 TUN. The netlink future is polled inline; no detached shell/route task or route-conflict reuse. Closing the owned FD removes associated routes, as documented at https://docs.kernel.org/networking/tuntap.html. Actual WS/QUIC peer addresses guard against capturing the hub connection. Pure preflight tests run without host effects; privileged rollback/default-route/DNS qualification remains outstanding. Exact upstream notices for the unmodified Linux netlink crates ship under assets/**/* and passed license review.
34
+ - Registry candidates are validated before any index mutation. Updates operate on a detached copy, reject ID/key/IP conflicts and malformed expiry/IPv4 assignments, and preserve the original record and indexes on failure. Malformed expiry also fails closed when checked outside the registry.
35
+ - Create/update settings now deserialize a complete detached candidate instead of silently clearing malformed security, filtering bad tags or truncating integers. Unknown/server-owned mutation fields reject explicitly; omitted fields are preserved, null clears optional settings, and a security object replaces the previous complete object. Shared ACL parsing preserves IPv4 exact/CIDR/wildcard/range syntax for both validation and matching. Startup validates preloaded client settings, connection block lists and destination policy before host effects. Rust and real daemon IPC regressions cover rejected updates/creates, allocation preservation, policy typos and null/VLAN invariants. This is input validation, not live-session or queued-packet revocation.
36
+ - ClientEntry, ClientSecurity and DestinationPolicyConfig omit absent optional fields when serializing, matching the public TypeScript output types; explicit null remains accepted for patch clearing. Registry admission consumes deprecated tags into serverDefinedClientTags and rejects both-populated aliases, so clearing and reimport cannot resurrect stale tags. No recursive JSON output shim is used.
37
+ - Native Noise admission captures a process-local record incarnation/revision and checks it again after the handshake response send, before local connection admission. Every successful registry mutation changes the revision; remove/recreate gets a new incarnation, even with identical keys. Invalid updates preserve the stamp. Registry guards are released before network sends, including unauthorized responses. Real Noise plus a controlled send barrier covers disable, disable/re-enable, remove/recreate, key rotation, expiry/policy edits and failed edits; WS/QUIC share this handler.
38
+ - Rate-limit unit tests use controlled Instant values through the same private consumption/refill path as production. The previous immediate-empty assertion was invalid at 1 MB/s: one microsecond refills a byte, and it failed under build load. Production still samples Instant::now; no test clock, sleeps or scheduler assumptions enter the runtime.
39
+ - IP pools require canonical network CIDRs and usable ranges; explicit reservations may be outside the dynamic range but not outside the subnet or on its network/gateway/broadcast addresses. Allocation is bounded, including a fully reserved pool and `/0`. `release_owned` compares an opaque reservation owner before release; callers that outlive records must use incarnation tokens, not reusable client IDs.
40
+ - Runtime address reservations now belong to registry incarnations, not reusable IDs or connections. Startup reserves all explicit registry and raw WG addresses before sorted dynamic allocation. Native admission verifies and uses the registry assignment; disconnect and registered WG peer removal retain it. Record removal disables/cancels, joins transports, reconciles the exact WG revision, then releases only the matching reservation. Failed create rollback rejects a changed revision, and failed rotation never restores old keys.
41
+ - LiveClients/ClientSession/SessionGuard owns one volatile generation across WS/QUIC and integrated WG. Admission counts the inline protocol lifetime before publishing its route. Cancellation closes task admission; reconnect joins predecessors outside registry/ownership locks. Protocol I/O and crypto drop before the guard token, and WG relays belong to both session and server trackers. Exact Arc identity fences stale route cleanup and merged WG returns. Raw WG control IDs use the full public key and a separate internal namespace; ambiguous control names reject without cancelling either owner.
42
+ - Native/WG idle expiry and registry mutations drain transport owners. WG disconnect resets BoringTun with a nonreused 24-bit peer index and retains configured peer settings. The WG loop interrupts blocked forwarding when a WG owner needs retirement; stale notifications from completed native takeovers must not drop the new WG session's first packet. Integrated UDP sends are nonblocking/best-effort, so send-buffer pressure cannot block other peers' revocation.
43
+ - Registered tunnel packets require exact-length, checksummed IPv4 headers and their authenticated assigned source. Outer connection ACLs and inner destination ACLs are separate. Hybrid forwarding uses the immutable session's useHostIp setting, not a packet-source lookup. Raw WG IPv6 remains kernel/testing only, never reinterpreted by IPv4 socket/bridge engines.
44
+ - Regressions cover address collision/reuse, joined native and real-UDP WG mutations, WS/QUIC socket takeover, cross-transport takeover with never-sent old ciphertext, idle expiry, stale cleanup/returns, unpolled descendants, raw peer reservations, source spoofing, malformed packets and failed reconciliation ownership. No publication or serve.zone production adoption is implied by this transport checkpoint.
45
+ - AuthenticatedPacket now carries ClientSession plus a tracked queued-work lifetime through socket/bridge queues. NatDispatcher selects a separate session-owned smoltcp stack; queued work, flow tasks/readers and replies no longer resolve a mutable registry/IP route. A reproduced registry-lock fail-open is removed: destination policy and PROXY metadata use the authenticated owner. Bridge writes observe current-client cancellation and broadcast shared retirement, so an unrelated blocked TAP write cannot indefinitely hold another client's queued stop token.
46
+ - NatFlow Arc identity fences TCP/UDP tuple reuse and owns a cancellation tracker plus a capacity lease. Each flow task/reader is enrolled with flow, client and server owners. Late task/message references retain the lease. Limits are 256 engines, 1,024 flow incarnations, and 128 flows/client by default (security.maxConnections may override the per-client limit). Packet/message queues are bounded. The existing smoltcp build has no fragment reassembly; IPv4 fragments, invalid TCP/UDP headers/checksums and bad lengths reject before flow allocation.
47
+ - A reproduced UDP demultiplexing defect sent different source-port flows through the first socket bound to their shared destination. There is now one smoltcp UDP listener per destination in each client stack; received endpoint metadata selects the exact flow. External UDP sockets are connected to their target, filtering unrelated senders. Tests cover real multi-client UDP/replies, same-target source-port isolation, full TCP packet handshake plus real bidirectional stream traffic, server/client revocation, stale tuple messages, queue drain, capacity retention and PROXY identity under registry lock.
48
+ - Server MTU enforcement now treats config as inner IP bytes (576..65472), rejects invalid input before startup effects, and checks authenticated ingress plus native/WG return encryption. A configured 1200 previously became 1121, and 1500-byte ingress previously passed a 1420-byte limit; both regressions were reproduced. Eligible DF packets get rate-limited IPv4 fragmentation-needed feedback from the owned gateway/bridge IP through the exact session. Malformed/error/fragment/multicast/broadcast packets do not provoke feedback; unavailable feedback never permits oversize. Real Noise and UDP/WG tests cover feedback, exact-limit admission and oversized return rejection. Counters distinguish drops from queued (not confirmed-sent) ICMP. This is not fragmentation/reassembly or outer path-MTU discovery.
49
+ - Native client MTU regressions reproduced acceptance of a 1200-byte packet despite a local 1100-byte ceiling, and fabricated getMtuInfo values both connected and disconnected. A required authenticated ClientHello now carries the local ceiling; the server pins the negotiated minimum on ClientSession and its smoltcp stack. Typed assignments include the actual subnet, reject missing/invalid values before TUN setup and remove guessed /24/MTU defaults. Real WS/QUIC tests cover negotiated return admission; real Noise plus unprivileged packet-I/O fixtures cover both client directions, exact-limit traffic, owned IPv4 feedback and retirement. The TCP NAT fixture exchanges 4KB while asserting every return packet respects the smaller session ceiling. Nullable MTU reporting describes actual native runtime ownership, not outer-path estimates; failed or short TUN feedback writes never count as sent. Both native endpoints must upgrade together.
50
+ - WireGuard's public flat config previously failed Rust deserialization with missing privateKey; the owning Rust input now consumes wgPrivateKey/serverPublicKey/wgAddress/wgEndpoint and requires nonempty wgAllowedIps, with no nested-peer adapter. Actual address/prefix/keys/MTU/AllowedIPs validate before TUN effects; route CIDRs normalize host bits, the configured prefix supplies netmask, and hostname resolution selects a matching UDP family. Explicit testing mode owns no TUN/routes; WG's existing default remains tun. Real UDP/BoringTun client-loop tests cover a complete handshake, bidirectional MTU admission, malformed/checksum and AllowedIPs source/destination rejection, exact-limit traffic, owned IPv4 feedback, short/blocked device writes, EOF retirement and actual UDP-port release. MTU IPC reports live local ownership, resets on reconnect, and remains null when inactive. Standard WireGuard framing and first-authorized-packet connection semantics are unchanged; connectedSince is now RFC3339.
51
+ - Managed snapshots now own hub policy and transport handover. Standalone kernel cleanup/partial-setup rollback and privileged managed host-network qualification remain outstanding. Raw WG IPv6 remains kernel/testing-only; oversized IPv6 drops without inventing an owned IPv6 router for feedback. No checkpoint retracts traffic already handed to an external network/kernel or proves production managed-network readiness.
52
+ - Return-queue baseline reproduced plaintext remaining available after session.stop acknowledged with no running consumer. ReturnQueues registers bounded per-session and shared WG queues with one LiveClients owner; queued ReturnPacket payloads retain destination and optional source join tokens. Either generation's cancellation purges every matching queue without consumer progress; dequeued writes observe both cancellation sources. Foreign runtime queues/relay origins reject. Native cancellation after encryption retires the destination stream because Noise's implicit nonce and partial frame cannot safely be continued; a real Noise blocked-write/reconnect test covers it. Real UDP/WG shared relay, multi-thread enqueue/revoke, either-owner purge/join and backpressure regressions cover the foundation now used by managed relay.
53
+ - BoringTun 0.7's encapsulate copies plaintext into a private queue when the current crypto session is absent; its timer removes that session after 180 seconds, before connection expiry. A regression reproduced revoked source plaintext escaping that queue after rekey. Integrated WG returns now check the public time_since_last_handshake Option under the same exclusive synchronous crypto owner: None drops/counts the application packet and requests a handshake without submitting plaintext. Some reflects the exact current-session slot used by encapsulate, not an age heuristic. The deterministic key-reset regression exercises rekey and proves the drained source cannot emit later while fresh destination returns still work. Explicit wall-clock qualification: `cargo test --manifest-path rust/Cargo.toml --lib return_crypto_timer_expiry_cannot_queue_plaintext_past_source_revocation -- --ignored`; it takes 181 seconds because BoringTun owns its clock. No dependency patch, timer override or networking privilege is used.
54
+ - Native client disconnect previously acknowledged a five-second timeout while a deliberately blocked packet write remained alive; the real packet-loop regression reproduced it. Native and WG clients now retain RuntimeTasks ownership and task handles. Disconnect cancels blocked I/O and awaits destruction; native route finalization stays outside packet cancellation, and native monitors retire on natural exit or panic through an exit guard. Task panic is reported as failure, not successful disconnect. Failed native/WG connection attempts reset false startup state. Tests cover blocked native packet I/O plus real keepalive destruction, native failed-connect retry, natural exit/panic, owned WG UDP socket release/panic, and actual WS/QUIC disconnect/reconnect. Full privileged client TUN/route qualification remains outstanding.
55
+ - Native and WireGuard client configs now form a discriminated union. Native configs require Noise keys; WireGuard configs require their own key/address/endpoint. The published `IVpnClientConfig` name remains available as the same union, while generated native bundles retain their narrower required-key type.
56
+ - Native unit tests are in the Cargo library target: `cargo test --lib` runs them. `cargo test --bin smartvpn_daemon` runs zero unit tests and is not a substitute.
57
+ - Facades consume published Smartrust 2.0's confirmed termination instead of fire-and-forget kill or a local timeout-as-success. Await all stop calls; server teardown drains startup and policy-health work, retains failed cleanup ownership, and requires a remote stop acknowledgement before removing socket-managed TUN policy. Whole-server packet task drain is now implemented below; per-client revocation and kernel-resource rollback still require qualification before publication as managed-network support.
58
+ - Rust server work shares an admission-fenced RuntimeTasks owner using tokio-util TaskTracker/CancellationToken. Stop cancels and drains listeners, pre-authentication/packet handlers, WG return relays, NAT flow tasks and their readers before clearing forwarding state; QUIC completes protocol shutdown. Loopback regressions check WS/QUIC/WG listener teardown and real bidirectional TCP/UDP socket traffic through the NAT bridge tasks. These are lifecycle tests, not complete authenticated packet-to-NAT policy tests. Startup waits for listener readiness and merges preloaded WG definitions before readiness, rejecting conflicting assignments and excluding disabled/expired registered keys. Existing kernel route/NAT cleanup helpers still swallow command failures, and partial host-network setup needs transactional cleanup; no privileged network test or production adoption has occurred.
59
+ - Run build and test commands sequentially: the current tsrust replaces `dist_rust` during every build, so overlapping builds can race test daemon discovery. A pre-ready child failure during overlapping verification was not attributable after stderr was discarded; focused and full sequential reruns passed. The facades now forward `stderr` for callers to capture diagnostics without automatic logging.
60
+
3
61
  ## Static Rust Binaries
4
62
 
5
- - The Rust binaries in `dist_rust/` are statically linked (static-pie) via `"static": true` in the `@git.zone/tsrust` block of `.smartconfig.json` (tsrust >= 1.4.1). They run on both glibc (Debian/Ubuntu) and musl (Alpine) systems.
6
- - tsrust injects `RUSTFLAGS="-C target-feature=+crt-static"` only into its own per-target cargo invocations and verifies the result (no `PT_INTERP` ELF header). Keep `rust/.cargo/config.toml` free of `rustflags` entries: the injected env variable would replace them, and a repo-wide `rustflags` would also break plain `cargo test`/`cargo check` (proc-macros cannot build with `+crt-static` on linux-gnu without an explicit `--target`).
63
+ - The Rust binaries in `dist_rust/` use explicit `linux_amd64_musl` and
64
+ `linux_arm64_musl` targets. tsrust 1.11 verifies static linking and writes
65
+ hash-bound provenance sidecars. The runtime chooses these exact filenames;
66
+ explicit SMARTVPN_RUST_BINARY selections never fall through to discovery.
67
+ - Target-specific Cargo CC settings require genuine musl headers for ring and
68
+ mimalloc. Final linking uses native/cross GCC drivers with Rust's self-contained
69
+ musl libraries; standalone musl-gcc specs add PT_INTERP even for static PIE and
70
+ are unsuitable as that final linker. Do not add repository-wide crt-static flags
71
+ that also affect untargeted host proc-macros.
7
72
  - `pnpm run test:rust` runs the Rust unit tests; plain `cargo test` in `rust/` works as well.
8
- - Verify linkage manually with `ldd dist_rust/<binary>_linux_amd64` "statically linked".
73
+ - Verify linkage with `file` and `readelf` against the `_musl` artifacts. Regression
74
+ tests reject PT_INTERP, DT_NEEDED, wrong machine identifiers and glibc signatures.
package/readme.md CHANGED
@@ -14,7 +14,7 @@ A high-performance VPN solution with a **TypeScript control plane** and a **Rust
14
14
  - 🔀 **Hybrid mode**: per-client routing — some clients bridge to the LAN, others use userspace NAT, all on the same server
15
15
  - 🏷️ **VLAN support**: assign individual clients to 802.1Q VLANs on the bridge
16
16
  - 🎯 **Destination routing policy**: force-target, block, or allow traffic per destination with nftables integration
17
- - ⚡ **Handshake-driven WireGuard state**: peers appear as "connected" only after a successful WireGuard handshake, and auto-disconnect on idle timeout
17
+ - ⚡ **Authenticated WireGuard state**: peers appear as "connected" after their first authenticated, source-authorized tunnel packet, and auto-disconnect on idle timeout
18
18
 
19
19
  ## Issue Reporting and Security
20
20
 
@@ -124,6 +124,53 @@ console.log(`Connected! VPN IP: ${assignedIp}`);
124
124
 
125
125
  ## Features ✨
126
126
 
127
+ ### Confirmed bridge shutdown
128
+
129
+ Always await `client.stop()`, `server.stop()` or `bridge.stop()` before replacing
130
+ an owned worker. These use Smartrust's confirmed termination API; a timeout is
131
+ not treated as successful shutdown. In stdio mode they wait for child exit and
132
+ stdio closure. In socket mode they confirm the local IPC connection has closed,
133
+ not that the externally supervised daemon process has exited.
134
+
135
+ `server.stop()` drains an in-flight start and firewall health check before
136
+ cleaning up its policy resources. Cleanup errors reject and retain ownership
137
+ for an explicit retry. If a socket-connected facade installed TUN policy, it
138
+ first requires the managed server's `stop` acknowledgement before closing IPC
139
+ and removing that policy. A socket connection without owned policy only closes
140
+ IPC; use `stopServer()` explicitly to request remote listener shutdown.
141
+
142
+ ```typescript
143
+ await client.stop();
144
+ await server.stop();
145
+ ```
146
+
147
+ This bridge lifecycle guarantee is separate from per-client packet/session
148
+ revocation inside the running Rust daemon; see `readme.hints.md` for the current
149
+ managed-network qualification boundaries.
150
+
151
+ The Rust server now waits for configured listeners and initial WireGuard peers
152
+ before acknowledging startup. A listener startup failure shuts down the owned
153
+ runtime. `stopServer()` cancels and drains server-owned packet tasks, including
154
+ pre-authentication connections, WireGuard return relays, and NAT socket readers;
155
+ QUIC also closes its connections and waits for protocol shutdown. Per-client
156
+ transport and queued forwarding teardown are described below. Transactional
157
+ rollback of standalone kernel network setup remains outstanding; the isolated
158
+ managed-client qualification below does not establish that guarantee for other modes.
159
+ When a raw WireGuard declaration uses a registered client's key, its `allowedIps`
160
+ must be exactly that client's assigned `/32`. Raw peers without a matching client
161
+ registry entry retain their separately configured prefix lists.
162
+
163
+ ### Client runtime shutdown
164
+
165
+ Always await `client.disconnect()`: it cancels and joins the native/WireGuard
166
+ packet runtime before returning, including the native keepalive monitor. A blocked
167
+ write cannot turn a timeout into a successful disconnect. Failed connection
168
+ attempts clear their startup state; unexpected packet-task panics remain errors.
169
+ Packet-loop exit clears connection and MTU metadata; native clients also retire
170
+ their monitor before reconnect can reuse the client. Dropping the Rust client requests cancellation but
171
+ cannot provide an awaited cleanup guarantee. Linux native managed-client TUN
172
+ cleanup is qualified below; standalone native/WireGuard host-route cleanup is not.
173
+
127
174
  ### 🔐 Enterprise Authentication (Noise IK)
128
175
 
129
176
  Every client authenticates with a **Noise IK handshake** (`Noise_IK_25519_ChaChaPoly_BLAKE2s`). The server verifies the client's static public key against its registry — unauthorized clients are rejected before any data flows.
@@ -164,7 +211,7 @@ console.log(stats.totalConnectionsQuic);
164
211
  console.log(stats.totalConnectionsWireguard);
165
212
  ```
166
213
 
167
- **WireGuard connection state is handshake-driven** — registered WireGuard peers do NOT appear as "connected" until their first successful WireGuard handshake completes. They automatically disconnect after 180 seconds of inactivity or when boringtun reports `ConnectionExpired`. This matches how WebSocket/QUIC clients behave: they appear on connection and disappear on disconnect.
214
+ **WireGuard connection state follows authenticated tunnel traffic** — a configured peer has no live return route until its first authenticated, source-authorized tunnel packet. Handshake or keepalive traffic alone does not admit a packet session. Peers disconnect after 180 seconds of inactivity, registry expiry, or when boringtun reports `ConnectionExpired`. Disconnect retains the configured peer but resets its cryptographic state, so subsequent traffic requires a fresh handshake. Raw peer connection IDs use `wg-` followed by the full public key.
168
215
 
169
216
  ### 🛡️ ACL Engine (SmartProxy-Aligned)
170
217
 
@@ -313,7 +360,61 @@ The **userspace NAT** mode extracts destination IP/port from IP packets, opens a
313
360
 
314
361
  The **bridge** mode assigns VPN clients IPs from a real LAN subnet instead of a virtual VPN subnet. Clients appear as if they're directly on the physical network — perfect for remote access to home labs, office networks, or IoT devices.
315
362
 
316
- The **hybrid** mode runs both engines simultaneously with a **per-client routing table**. Each client's `useHostIp` flag determines whether its packets go through the bridge (L2, LAN IP) or socket NAT (userspace, VPN IP). This is ideal when most clients need internet NAT but some need direct LAN access.
363
+ The **hybrid** mode runs both engines simultaneously. Each authenticated session's `useHostIp` setting determines whether its packets go through the bridge or socket NAT; routing does not look up identity from the packet source.
364
+
365
+ ### Inner packet MTU
366
+
367
+ `mtu` means inner IP bytes, not the outer network link MTU. It defaults to 1420
368
+ and must be an integer from 576 through 65472, leaving room for the supported
369
+ Noise and WireGuard framing. Larger previous values now reject before startup
370
+ effects. The server uses this same ceiling for its TUN/TAP, userspace stack and
371
+ native assignment; it does not subtract an estimated encapsulation overhead.
372
+
373
+ Native clients now send their receive ceiling inside the authenticated Noise IK
374
+ handshake. The server retains `min(server MTU, client MTU)` on that session,
375
+ advertises it with the actual assignment subnet, and applies it to ingress,
376
+ return encryption and the session's userspace TCP stack. Client configuration
377
+ defaults to 1420; the client uses the negotiated ceiling unchanged for its TUN
378
+ and both packet directions. Assignment fields must be valid before device setup;
379
+ there is no guessed `/24`, gateway or MTU fallback. This changes the native wire
380
+ contract: both ends must be upgraded together. Standard WireGuard framing is unchanged.
381
+
382
+ Eligible oversized outbound client IPv4 packets receive feedback through the
383
+ client TUN using its assigned address and subnet. An incomplete or failed device
384
+ write is an error, never counted as successful feedback or retried as a second
385
+ packet. Oversized inbound packets are discarded before TUN writes.
386
+
387
+ WireGuard clients use the public flat `wgPrivateKey`, `serverPublicKey`,
388
+ `wgAddress`, `wgAddressPrefix`, `wgEndpoint` and nonempty `wgAllowedIps` fields.
389
+ The old Rust-only nested `peer` input is removed, with no translation fallback.
390
+ Addresses, prefixes, keys, endpoint and MTU validate before TUN setup. The configured
391
+ prefix supplies the device netmask; AllowedIPs route networks are canonicalized.
392
+ `forwardingMode: 'testing'` creates no TUN or routes; the existing WireGuard default
393
+ remains `'tun'` (native defaults to `'testing'`). Endpoint hostnames resolve in Rust.
394
+ AllowedIPs authorizes decrypted source addresses and outbound destinations, after
395
+ validating IP headers. The local configured MTU applies unchanged in both directions;
396
+ standard WireGuard has no native SmartVPN MTU negotiation.
397
+
398
+ `client.getMtuInfo()` now reports the live native or WireGuard runtime, or `null` when inactive.
399
+ `tunMtu` is `null` in testing mode because no interface exists; unmeasured
400
+ `linkMtu` and `overheadBytes` are also `null`. Counters belong to the current
401
+ connection and `icmpTooBigSent` counts only complete TUN writes. These nullable
402
+ results replace the previous hard-coded values.
403
+
404
+ Authenticated native and integrated WireGuard packets are size-checked after
405
+ source/ACL/rate admission and before forwarding. Return packets are checked before
406
+ encryption. Oversized packets are dropped even when no safe error can be sent.
407
+ Eligible IPv4 DF packets receive a bounded fragmentation-needed response from the
408
+ owned tunnel gateway or bridge host address, through their exact session. Invalid
409
+ headers, ICMP errors, noninitial fragments, multicast and known broadcasts do not
410
+ generate errors. Feedback is limited to 10 packets/second with a burst of 10 per
411
+ server; internal accounting distinguishes dropped packets from feedback queued.
412
+
413
+ This is an inner admission ceiling, not IP fragmentation/reassembly or measured
414
+ path-MTU discovery. Raw WG IPv6 remains kernel/testing-only and oversized IPv6 is
415
+ dropped without fabricating an IPv6 router address. Standalone host-network
416
+ cleanup and privileged WireGuard TUN qualification remain outstanding; the
417
+ unprivileged packet fixtures do not establish host readiness for those modes.
317
418
 
318
419
  ### 🏠 Per-Client Bridge & VLAN Settings
319
420
 
@@ -365,7 +466,7 @@ VLAN support uses Linux bridge VLAN filtering — each client's TAP port gets ta
365
466
  - **Adaptive keepalives**: Interval adjusts based on link health (60s → 30s → 10s)
366
467
  - **Per-client rate limiting**: Token bucket with configurable bytes/sec and burst
367
468
  - **Dead-peer detection**: 180s inactivity timeout (all transports)
368
- - **MTU management**: Automatic overhead calculation (IP+TCP+WS+Noise = 79 bytes)
469
+ - **MTU management**: Enforced inner packet ceilings and live counters; unmeasured outer-path values remain null
369
470
  - **Per-transport stats**: Active client and total connection counts broken down by websocket, QUIC, and WireGuard
370
471
 
371
472
  ### 🏷️ Client Tags (Trusted vs Informational)
@@ -396,6 +497,83 @@ await client.connect({
396
497
 
397
498
  The server acts as a **hub** — one API to manage all clients:
398
499
 
500
+ `createClient()` accepts `IClientCreateOptions` with a required `clientId`;
501
+ `updateClient()` accepts `TClientUpdateOptions`. Both reject unknown fields,
502
+ server-owned identity/key/address fields, malformed security, invalid IPv4 ACL
503
+ patterns, out-of-range integers and invalid VLAN combinations. Rejected settings
504
+ do not change the registry or consume a new address. `forceTarget` requires an
505
+ explicit IPv4 target; malformed policies are also rejected at server startup.
506
+
507
+ Omitting an update field preserves it; explicit `null` clears an optional setting.
508
+ Responses omit absent optional fields (including nested security/policy fields),
509
+ matching the TypeScript interfaces; `null` is a clearing input, not an output value.
510
+ A supplied `security` object replaces the whole security object, not individual
511
+ nested fields. Clear `forceVlan` together with `vlanId`; an enabled VLAN requires
512
+ an ID from 1 to 4094. Use `serverDefinedClientTags`; the shipped deprecated `tags`
513
+ input remains accepted, but supplying both names is rejected as ambiguous.
514
+ Preloaded entries also reject both populated tag fields; accepted old tags are
515
+ consumed into the canonical field and are not retained in registry output.
516
+ Input validation is separate from transport ownership and queued forwarding
517
+ revocation; the runtime ownership rules below apply after successful admission.
518
+
519
+ Native WebSocket/QUIC handshakes also recheck the exact registry incarnation and
520
+ revision after sending the Noise response, before local admission. A successful
521
+ update, disable/re-enable or remove/recreate invalidates an in-flight handshake;
522
+ a rejected update does not. Network response writes do not hold the registry
523
+ lock.
524
+
525
+ Native and integrated WireGuard transports share one live session owner per
526
+ registered client. Authenticated reconnect cancels and joins the previous
527
+ transport generation before admitting its replacement. Disconnect, successful
528
+ settings changes, disable, expiry, removal and key rotation retire the old
529
+ transport and its WireGuard return relay. Queued WireGuard returns carry their
530
+ original session identity and cannot target a replacement. Failed key-rotation
531
+ reconciliation does not restore old credentials; inspect the registry and retry
532
+ the operation to obtain fresh credentials.
533
+
534
+ Registry assignments are reserved before listeners start and reused unchanged
535
+ across native/WireGuard connections. Disconnect retains that reservation; record
536
+ removal releases it only after transport teardown. Explicit assignments, including
537
+ raw WireGuard reservations, precede dynamic allocation. A failed create cannot
538
+ release an address belonging to a concurrent registry revision or recreated record.
539
+ Decrypted registered-client packets must have a valid IPv4 header and exactly the
540
+ assigned source address. Connection ACLs use the outer transport endpoint;
541
+ destination ACLs use the inner packet destination. Runtime rate-limit adjustments
542
+ apply to the current session only; use registry security settings for reconnects.
543
+
544
+ Socket and bridge forwarding queues retain authenticated session ownership until
545
+ their packet work is destroyed. Socket mode gives each session its own smoltcp
546
+ stack; policy and PROXY identity come from that captured session, never a mutable
547
+ registry lookup by packet source. Its TCP/UDP flows and socket readers are joined
548
+ on revocation, and replies return only to their original owner. Flow incarnations
549
+ also fence late responses when a tuple is reused. UDP flows sharing a destination
550
+ are selected by received source endpoint, and real UDP sockets accept replies only
551
+ from their configured target.
552
+
553
+ Return queues are also generation-owned: cancellation purges queued payloads
554
+ without waiting for a consumer, including WireGuard's shared return queue.
555
+ Dequeued packets retain their destination lifetime, and internal relay packets
556
+ retain their source lifetime too, until I/O finishes or the payload is destroyed.
557
+ An interrupted native ciphertext write retires that destination tunnel rather
558
+ than continuing a partially written frame or an advanced Noise nonce sequence.
559
+ WireGuard returns require a current encryption session. While rekeying without
560
+ one, application packets are dropped and counted; only a handshake is requested,
561
+ so plaintext cannot escape owner cancellation through BoringTun's private queue.
562
+ Queues are bounded (256 per session, 1024 for shared WG returns); full or closed
563
+ queues reject admission. Managed relay uses these same two-owner lifetimes.
564
+
565
+ Socket NAT is bounded to 256 active engines and 1,024 flow incarnations per server
566
+ dispatcher. The per-client default is 128 TCP/UDP flows combined; a registered
567
+ client's `security.maxConnections` overrides that local limit without bypassing
568
+ the global cap. Late task/message references retain capacity until destroyed.
569
+ Protocol and dispatch queues are bounded. This IPv4 userspace build does not
570
+ reassemble fragments and rejects them before creating flows.
571
+
572
+ These guarantees cover daemon-owned work, not traffic already handed to an
573
+ external network or kernel. Managed relay is described below. Transactional cleanup
574
+ and privileged qualification of standalone kernel modes remain outstanding;
575
+ packet fixtures do not prove production network readiness.
576
+
399
577
  ```typescript
400
578
  // Create (generates keys, assigns IP, returns config bundle)
401
579
  const bundle = await server.createClient({ clientId: 'bob-phone' });
@@ -424,6 +602,108 @@ const wgConf = await server.exportClientConfig('bob-phone', 'wireguard');
424
602
  await server.removeClient('bob-phone');
425
603
  ```
426
604
 
605
+ ### Caller-owned managed networks
606
+
607
+ `forwardingMode: 'managed'` is an exclusive IPv4 node relay. The hub creates no
608
+ TUN, NAT, bridge or host routes. Start it with a fixed `subnet` and required
609
+ `managedAuthorityId`, then call `reconcileManagedNetwork(snapshot)`. Cloudly (or
610
+ another trusted controller) owns durable node IDs, keys, control addresses,
611
+ workload prefixes and policy; SmartVPN owns volatile enforcement, not persistence.
612
+ Protect the management IPC endpoint: the authority ID is a binding, not a credential.
613
+
614
+ ```typescript
615
+ import type { IManagedNetworkSnapshot } from '@push.rocks/smartvpn';
616
+
617
+ const snapshot: IManagedNetworkSnapshot = {
618
+ schemaVersion: 1,
619
+ authorityId: 'cloudly',
620
+ revision: 1,
621
+ nodes: [{
622
+ nodeId: 'worker-a',
623
+ publicKey: '<caller-owned-noise-public-key-base64>',
624
+ controlAddress: '10.88.0.2',
625
+ controlPolicyDomain: 'control',
626
+ enabled: true,
627
+ workloadPrefixes: [{ cidr: '10.90.0.0/24', policyDomain: 'task.vc' }],
628
+ }],
629
+ grants: [], // Absent grants deny, including within the same domain.
630
+ };
631
+ const applied = await server.reconcileManagedNetwork(snapshot);
632
+ const status = await server.getManagedNetworkStatus();
633
+ const projection = await server.getManagedNodeProjection('worker-a');
634
+ ```
635
+
636
+ Snapshots are complete replacements. Canonical array order makes exact replay
637
+ idempotent; stale revisions and conflicting equal revisions reject. Revisions
638
+ are positive JSON-safe integers scoped to `lifetimeId`. Every daemon restart
639
+ starts empty and requires the caller to reconcile its current durable state.
640
+ Node keys must be unique canonical/contributory X25519 keys, including across
641
+ Noise and WireGuard. Control addresses are exact usable hosts in the fixed subnet;
642
+ workload prefixes cannot overlap that subnet or any other owned prefix.
643
+
644
+ Grants are explicit `(sourceDomain, destinationDomain)` pairs. They neither imply
645
+ same-domain permission nor reverse/reply permission. Different workload prefixes
646
+ on one node may belong to different domains. Disabled, expired, missing, offline
647
+ or denied destinations drop without host fallback. Traffic that never crosses
648
+ the hub needs local enforcement by the node executor; local workload prefixes
649
+ are never routed back to the hub merely because a grant involves them.
650
+
651
+ Invalid preparation preserves active authority. A valid change quarantines and
652
+ cancels only affected node generations, joins their packet work, prepares WG
653
+ crypto and commits the whole snapshot atomically before acknowledging. Unchanged
654
+ nodes keep their sessions. A post-drain failure leaves `state: 'failed'`, its
655
+ `pendingRevision` and bounded `lastError`; old affected admission stays closed.
656
+ Only the exact pending snapshot can retry. An IPC timeout or disconnected caller
657
+ does not cancel the process-owned operation. Inspect status and replay that exact
658
+ target; never assume a timed-out request did not commit. Separate Unix IPC
659
+ connections can inspect status while apply drains; stdio remains sequential.
660
+
661
+ Standalone create/update/remove/enable/disable/rotate/disconnect/rate-limit and
662
+ raw WG-peer mutations reject in managed mode, including direct WG-loop commands.
663
+ Standalone startup clients, raw peers, DNS, NAT, bridge and destination-routing
664
+ configuration reject before effects. Managed WireGuard requires an explicit
665
+ `serverEndpoint` and starts without peers until reconciliation.
666
+
667
+ Native clients opt in with `managedNetwork: { authorityId: 'cloudly', nodeId: 'worker-a' }`.
668
+ Missing, unexpected or differently bound managed assignments reject before host
669
+ effects; a managed client cannot downgrade to a standalone subnet assignment.
670
+ Native encrypted assignments carry schema, node/authority/lifetime/revision,
671
+ local ownership and remote outgoing routes. Client `getStatus().managedNetwork`
672
+ reports only the live validated assignment and clears on retirement. Native Linux
673
+ TUN setup uses a fresh nonpersistent /32 device with strict exclusive route adds,
674
+ not shell commands or route-conflict suppression. Preflight rejects malformed,
675
+ overlapping, default, local-prefix and active-hub-endpoint capture. The netlink
676
+ owner is polled inline; failure/cancellation drops the device and its owned routes.
677
+ No DNS or default-route mutation is requested; native `forwardingMode: 'testing'`
678
+ has no host effects. Managed TUN requires Linux.
679
+
680
+ The Linux amd64 musl build passed the isolated `serve.zone/testing` VM scenario:
681
+ real WS/Noise transport and TUN workload traffic between nodes connected only to
682
+ the hub, directed allow/deny, reconnect, natural revocation, partial route-conflict
683
+ rollback, empty-authority restart, and child termination. Cleanup removed owned
684
+ namespaces and preserved DNS and IPv4/IPv6 default routes. This qualification
685
+ does not cover WSS/private CAs, IPv6 workload traffic, privileged ARM64 networking,
686
+ standalone kernel modes, or Pallet/Cloudly production integration.
687
+
688
+ WireGuard has no assignment channel. `getManagedNodeProjection(nodeId)` returns
689
+ the applied control `/32`, per-prefix domains, outgoing destinations, incoming
690
+ sources, MTU and optional public WG configuration, never a private key. Client
691
+ WG AllowedIPs is the union of remote outgoing/incoming prefixes plus the hub's
692
+ MTU-feedback gateway `/32`; the hub independently enforces direction. Server WG
693
+ AllowedIPs contains the node's owned source prefixes. Authenticated WG keepalive
694
+ confirmation admits receive-only managed nodes without an inner probe. Projection
695
+ `ready` means applied/admissible configuration, not connectivity, node installation
696
+ or deployment readiness; disabled, absent and quarantined nodes return no config.
697
+ The controller must apply WG projections through its owned node runtime.
698
+
699
+ Bounds are 1,024 nodes, 128 workload prefixes per node, 16,384 owned prefixes,
700
+ 16,384 grants, 128-byte IDs, 1,024 routes per node per direction, and 262,144 total
701
+ projected routes/grants. Managed relay enforces both live negotiated MTUs and
702
+ returns eligible IPv4 fragmentation-needed feedback to the exact source owner.
703
+ The complete [third-party notice index](./third-party-notices.md) covers the locked
704
+ Cargo graph and statically linked compiler/runtime components. Keep its accompanying
705
+ notice files with separately redistributed executables.
706
+
427
707
  ### 📝 WireGuard Config Generation
428
708
 
429
709
  Generate standard `.conf` files for any WireGuard client:
@@ -493,6 +773,7 @@ client.on('exit', ({ code, signal }) => { /* daemon process exited */ });
493
773
  |-------|-----------|---------|
494
774
  | `exit` | Both | `{ code, signal }` — daemon process exited |
495
775
  | `reconnected` | Both | `void` — socket transport reconnected |
776
+ | `stderr` | Both | `string` — daemon diagnostic line, forwarded without automatic logging |
496
777
 
497
778
  For connection state and telemetry, use `getStatus()`, `getStatistics()`, `listClients()`, and `getClientTelemetry()`.
498
779
 
@@ -532,6 +813,8 @@ For connection state and telemetry, use `getStatus()`, `getStatistics()`, `listC
532
813
  | Command | Description |
533
814
  |---------|-------------|
534
815
  | `start` / `stop` | Start/stop the VPN listener |
816
+ | `reconcileManagedNetwork` | Validate, drain and atomically apply a complete managed snapshot |
817
+ | `getManagedNetworkStatus` / `getManagedNodeProjection` | Authority/lifetime/revision status and caller-applied node configuration |
535
818
  | `createClient` | Generate keys, assign IP, return config bundle |
536
819
  | `removeClient` / `getClient` / `listRegisteredClients` | Client registry CRUD |
537
820
  | `updateClient` / `enableClient` / `disableClient` | Modify client state |
@@ -615,6 +898,31 @@ All frames use `[type:1B][length:4B][payload:NB]` with a 64KB max payload:
615
898
 
616
899
  ## Development 🛠️
617
900
 
901
+ ### Native binary selection and build prerequisites
902
+
903
+ The package builds `smartvpn_daemon_linux_amd64_musl` and
904
+ `smartvpn_daemon_linux_arm64_musl` with the pinned Rust 1.95.0 toolchain and
905
+ locked Cargo dependencies. Both targets require genuine musl C toolchains for
906
+ the native `ring` and `mimalloc` dependencies. Put `x86_64-linux-musl-gcc` and
907
+ `aarch64-linux-musl-gcc` on `PATH` before building or testing; GNU libc headers
908
+ are not a substitute. The existing build and test commands remain unchanged.
909
+
910
+ In stdio mode the bridge selects exactly the bundled executable for Linux x64
911
+ or arm64. `SMARTVPN_RUST_BINARY` selects an explicit development executable on
912
+ any supported native build platform. Missing, non-executable, and empty explicit
913
+ selections fail: there is no fallback to old GNU artifacts, the current working
914
+ directory's Rust builds, platform packages, or a daemon found on `PATH`.
915
+ Other platforms must provide that explicit executable. Socket transport connects
916
+ to an independently managed daemon and does not require a local binary.
917
+
918
+ For an unprivileged build host, musl's upstream `musl-gcc` wrappers can provide
919
+ the C toolchains using existing native and cross GCC compilers. Build musl 1.2.5
920
+ in separate disposable prefixes for each architecture, with `--disable-shared`
921
+ and an explicitly prefix-local `--syslibdir`. Use the two musl security patches
922
+ from [Rust 1.95.0's build recipe](https://github.com/rust-lang/rust/blob/1.95.0/src/ci/docker/scripts/musl-toolchain.sh).
923
+ Expose the generated wrappers under the target-specific names above; never
924
+ install a replacement libc or dynamic linker into the host's system directories.
925
+
618
926
  ```bash
619
927
  # Install dependencies
620
928
  pnpm install
@@ -674,7 +982,7 @@ smartvpn/
674
982
  │ └── reconnect.rs # Exponential backoff + session tokens
675
983
  ├── test/ # Test files
676
984
  ├── dist_ts/ # Compiled TypeScript
677
- └── dist_rust/ # Cross-compiled binaries (linux amd64 + arm64)
985
+ └── dist_rust/ # Static musl binaries (Linux amd64 + arm64)
678
986
  ```
679
987
 
680
988
  ## License and Legal Information