@push.rocks/smartvpn 1.22.0 → 2.1.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 (40) 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 +4 -3
  8. package/dist_ts/smartvpn.classes.vpnbridge.js +27 -47
  9. package/dist_ts/smartvpn.classes.vpnclient.d.ts +5 -4
  10. package/dist_ts/smartvpn.classes.vpnclient.js +10 -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 +155 -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 +15 -7
  30. package/readme.md +344 -5
  31. package/third-party-notices.md +81 -0
  32. package/ts/00_commitinfo_data.ts +1 -1
  33. package/ts/smartvpn.classes.vpnbridge.ts +27 -52
  34. package/ts/smartvpn.classes.vpnclient.ts +10 -5
  35. package/ts/smartvpn.classes.vpnconfig.ts +49 -13
  36. package/ts/smartvpn.classes.vpnserver.ts +94 -40
  37. package/ts/smartvpn.interfaces.ts +159 -16
  38. package/ts/smartvpn.paths.ts +19 -0
  39. package/readme.hints.md +0 -8
  40. package/readme.plan.md +0 -253
@@ -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.22.0",
3
+ "version": "2.1.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,26 +25,34 @@
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
- "@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"
31
+ "@git.zone/tsbuild": "^4.4.3",
32
+ "@git.zone/tsrun": "^2.0.6",
33
+ "@git.zone/tsrust": "^1.13.0",
34
+ "@git.zone/tstest": "^6.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
  ],
45
47
  "browserslist": [
46
48
  "last 1 chrome versions"
47
49
  ],
50
+ "publishConfig": {
51
+ "executableFiles": [
52
+ "./dist_rust/smartvpn_daemon_linux_amd64_musl",
53
+ "./dist_rust/smartvpn_daemon_linux_arm64_musl"
54
+ ]
55
+ },
48
56
  "scripts": {
49
57
  "build": "(tsbuild tsfolders) && (tsrust)",
50
58
  "test:before": "(tsrust)",
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,84 @@ console.log(`Connected! VPN IP: ${assignedIp}`);
124
124
 
125
125
  ## Features ✨
126
126
 
127
+ ### Caller-owned client network namespace
128
+
129
+ A Linux stdio client accepts `networkNamespaceFd` in `IVpnClientOptions`. Pass an
130
+ already open network namespace descriptor; the caller retains ownership and keeps
131
+ it valid through each `start()`. The child validates its inherited duplicate,
132
+ enters the namespace before creating runtime threads or sockets, then closes the
133
+ duplicate. Invalid descriptors and failed entry cannot produce a ready client.
134
+ Stopping the client waits for process termination before the caller releases its
135
+ namespace owner. A running child remains in its namespace if the caller closes
136
+ the original descriptor, but a later start still requires a valid descriptor.
137
+
138
+ The caller must provide the hub endpoint's underlay route and any required DNS
139
+ reachability inside that namespace before connecting. SmartVPN creates neither
140
+ the namespace nor that underlay. Managed TUN setup still validates the authenticated
141
+ assignment and rejects routes that capture the actual hub endpoint. Namespace
142
+ placement does not grant workload access or replace packet policy.
143
+
144
+ The option is process configuration, separate from VPN connection configuration.
145
+ It is rejected for socket transport and server bridges. The native equivalent is
146
+ `--management --mode client --network-namespace-fd 3`, with an inherited descriptor
147
+ at FD 3. Server facade firewall setup runs in its parent process, so server
148
+ namespace placement requires a separately coordinated policy owner.
149
+
150
+ The Linux amd64 musl candidate passed offline Linux 6.18.35 qualification with
151
+ all native threads in the target namespace, child descriptor closure, retained
152
+ caller ownership and denied-entry rejection. The shared `serve.zone/testing`
153
+ scenario also passed real WS/Noise traffic through inherited namespace entry,
154
+ managed TUN creation, allow/deny, reconnect, revocation, route-conflict rollback
155
+ and child-termination cleanup. Privileged ARM64 and Pallet integration remain
156
+ separate qualification requirements.
157
+
158
+ ### Confirmed bridge shutdown
159
+
160
+ Always await `client.stop()`, `server.stop()` or `bridge.stop()` before replacing
161
+ an owned worker. These use Smartrust's confirmed termination API; a timeout is
162
+ not treated as successful shutdown. In stdio mode they wait for child exit and
163
+ stdio closure. In socket mode they confirm the local IPC connection has closed,
164
+ not that the externally supervised daemon process has exited.
165
+
166
+ `server.stop()` drains an in-flight start and firewall health check before
167
+ cleaning up its policy resources. Cleanup errors reject and retain ownership
168
+ for an explicit retry. If a socket-connected facade installed TUN policy, it
169
+ first requires the managed server's `stop` acknowledgement before closing IPC
170
+ and removing that policy. A socket connection without owned policy only closes
171
+ IPC; use `stopServer()` explicitly to request remote listener shutdown.
172
+
173
+ ```typescript
174
+ await client.stop();
175
+ await server.stop();
176
+ ```
177
+
178
+ This bridge lifecycle guarantee is separate from per-client packet/session
179
+ revocation inside the running Rust daemon; see the managed-network qualification
180
+ boundaries below.
181
+
182
+ The Rust server now waits for configured listeners and initial WireGuard peers
183
+ before acknowledging startup. A listener startup failure shuts down the owned
184
+ runtime. `stopServer()` cancels and drains server-owned packet tasks, including
185
+ pre-authentication connections, WireGuard return relays, and NAT socket readers;
186
+ QUIC also closes its connections and waits for protocol shutdown. Per-client
187
+ transport and queued forwarding teardown are described below. Transactional
188
+ rollback of standalone kernel network setup remains outstanding; the isolated
189
+ managed-client qualification below does not establish that guarantee for other modes.
190
+ When a raw WireGuard declaration uses a registered client's key, its `allowedIps`
191
+ must be exactly that client's assigned `/32`. Raw peers without a matching client
192
+ registry entry retain their separately configured prefix lists.
193
+
194
+ ### Client runtime shutdown
195
+
196
+ Always await `client.disconnect()`: it cancels and joins the native/WireGuard
197
+ packet runtime before returning, including the native keepalive monitor. A blocked
198
+ write cannot turn a timeout into a successful disconnect. Failed connection
199
+ attempts clear their startup state; unexpected packet-task panics remain errors.
200
+ Packet-loop exit clears connection and MTU metadata; native clients also retire
201
+ their monitor before reconnect can reuse the client. Dropping the Rust client requests cancellation but
202
+ cannot provide an awaited cleanup guarantee. Linux native managed-client TUN
203
+ cleanup is qualified below; standalone native/WireGuard host-route cleanup is not.
204
+
127
205
  ### 🔐 Enterprise Authentication (Noise IK)
128
206
 
129
207
  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 +242,7 @@ console.log(stats.totalConnectionsQuic);
164
242
  console.log(stats.totalConnectionsWireguard);
165
243
  ```
166
244
 
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.
245
+ **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
246
 
169
247
  ### 🛡️ ACL Engine (SmartProxy-Aligned)
170
248
 
@@ -313,7 +391,61 @@ The **userspace NAT** mode extracts destination IP/port from IP packets, opens a
313
391
 
314
392
  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
393
 
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.
394
+ 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.
395
+
396
+ ### Inner packet MTU
397
+
398
+ `mtu` means inner IP bytes, not the outer network link MTU. It defaults to 1420
399
+ and must be an integer from 576 through 65472, leaving room for the supported
400
+ Noise and WireGuard framing. Larger previous values now reject before startup
401
+ effects. The server uses this same ceiling for its TUN/TAP, userspace stack and
402
+ native assignment; it does not subtract an estimated encapsulation overhead.
403
+
404
+ Native clients now send their receive ceiling inside the authenticated Noise IK
405
+ handshake. The server retains `min(server MTU, client MTU)` on that session,
406
+ advertises it with the actual assignment subnet, and applies it to ingress,
407
+ return encryption and the session's userspace TCP stack. Client configuration
408
+ defaults to 1420; the client uses the negotiated ceiling unchanged for its TUN
409
+ and both packet directions. Assignment fields must be valid before device setup;
410
+ there is no guessed `/24`, gateway or MTU fallback. This changes the native wire
411
+ contract: both ends must be upgraded together. Standard WireGuard framing is unchanged.
412
+
413
+ Eligible oversized outbound client IPv4 packets receive feedback through the
414
+ client TUN using its assigned address and subnet. An incomplete or failed device
415
+ write is an error, never counted as successful feedback or retried as a second
416
+ packet. Oversized inbound packets are discarded before TUN writes.
417
+
418
+ WireGuard clients use the public flat `wgPrivateKey`, `serverPublicKey`,
419
+ `wgAddress`, `wgAddressPrefix`, `wgEndpoint` and nonempty `wgAllowedIps` fields.
420
+ The old Rust-only nested `peer` input is removed, with no translation fallback.
421
+ Addresses, prefixes, keys, endpoint and MTU validate before TUN setup. The configured
422
+ prefix supplies the device netmask; AllowedIPs route networks are canonicalized.
423
+ `forwardingMode: 'testing'` creates no TUN or routes; the existing WireGuard default
424
+ remains `'tun'` (native defaults to `'testing'`). Endpoint hostnames resolve in Rust.
425
+ AllowedIPs authorizes decrypted source addresses and outbound destinations, after
426
+ validating IP headers. The local configured MTU applies unchanged in both directions;
427
+ standard WireGuard has no native SmartVPN MTU negotiation.
428
+
429
+ `client.getMtuInfo()` now reports the live native or WireGuard runtime, or `null` when inactive.
430
+ `tunMtu` is `null` in testing mode because no interface exists; unmeasured
431
+ `linkMtu` and `overheadBytes` are also `null`. Counters belong to the current
432
+ connection and `icmpTooBigSent` counts only complete TUN writes. These nullable
433
+ results replace the previous hard-coded values.
434
+
435
+ Authenticated native and integrated WireGuard packets are size-checked after
436
+ source/ACL/rate admission and before forwarding. Return packets are checked before
437
+ encryption. Oversized packets are dropped even when no safe error can be sent.
438
+ Eligible IPv4 DF packets receive a bounded fragmentation-needed response from the
439
+ owned tunnel gateway or bridge host address, through their exact session. Invalid
440
+ headers, ICMP errors, noninitial fragments, multicast and known broadcasts do not
441
+ generate errors. Feedback is limited to 10 packets/second with a burst of 10 per
442
+ server; internal accounting distinguishes dropped packets from feedback queued.
443
+
444
+ This is an inner admission ceiling, not IP fragmentation/reassembly or measured
445
+ path-MTU discovery. Raw WG IPv6 remains kernel/testing-only and oversized IPv6 is
446
+ dropped without fabricating an IPv6 router address. Standalone host-network
447
+ cleanup and privileged WireGuard TUN qualification remain outstanding; the
448
+ unprivileged packet fixtures do not establish host readiness for those modes.
317
449
 
318
450
  ### 🏠 Per-Client Bridge & VLAN Settings
319
451
 
@@ -365,7 +497,7 @@ VLAN support uses Linux bridge VLAN filtering — each client's TAP port gets ta
365
497
  - **Adaptive keepalives**: Interval adjusts based on link health (60s → 30s → 10s)
366
498
  - **Per-client rate limiting**: Token bucket with configurable bytes/sec and burst
367
499
  - **Dead-peer detection**: 180s inactivity timeout (all transports)
368
- - **MTU management**: Automatic overhead calculation (IP+TCP+WS+Noise = 79 bytes)
500
+ - **MTU management**: Enforced inner packet ceilings and live counters; unmeasured outer-path values remain null
369
501
  - **Per-transport stats**: Active client and total connection counts broken down by websocket, QUIC, and WireGuard
370
502
 
371
503
  ### 🏷️ Client Tags (Trusted vs Informational)
@@ -396,6 +528,83 @@ await client.connect({
396
528
 
397
529
  The server acts as a **hub** — one API to manage all clients:
398
530
 
531
+ `createClient()` accepts `IClientCreateOptions` with a required `clientId`;
532
+ `updateClient()` accepts `TClientUpdateOptions`. Both reject unknown fields,
533
+ server-owned identity/key/address fields, malformed security, invalid IPv4 ACL
534
+ patterns, out-of-range integers and invalid VLAN combinations. Rejected settings
535
+ do not change the registry or consume a new address. `forceTarget` requires an
536
+ explicit IPv4 target; malformed policies are also rejected at server startup.
537
+
538
+ Omitting an update field preserves it; explicit `null` clears an optional setting.
539
+ Responses omit absent optional fields (including nested security/policy fields),
540
+ matching the TypeScript interfaces; `null` is a clearing input, not an output value.
541
+ A supplied `security` object replaces the whole security object, not individual
542
+ nested fields. Clear `forceVlan` together with `vlanId`; an enabled VLAN requires
543
+ an ID from 1 to 4094. Use `serverDefinedClientTags`; the shipped deprecated `tags`
544
+ input remains accepted, but supplying both names is rejected as ambiguous.
545
+ Preloaded entries also reject both populated tag fields; accepted old tags are
546
+ consumed into the canonical field and are not retained in registry output.
547
+ Input validation is separate from transport ownership and queued forwarding
548
+ revocation; the runtime ownership rules below apply after successful admission.
549
+
550
+ Native WebSocket/QUIC handshakes also recheck the exact registry incarnation and
551
+ revision after sending the Noise response, before local admission. A successful
552
+ update, disable/re-enable or remove/recreate invalidates an in-flight handshake;
553
+ a rejected update does not. Network response writes do not hold the registry
554
+ lock.
555
+
556
+ Native and integrated WireGuard transports share one live session owner per
557
+ registered client. Authenticated reconnect cancels and joins the previous
558
+ transport generation before admitting its replacement. Disconnect, successful
559
+ settings changes, disable, expiry, removal and key rotation retire the old
560
+ transport and its WireGuard return relay. Queued WireGuard returns carry their
561
+ original session identity and cannot target a replacement. Failed key-rotation
562
+ reconciliation does not restore old credentials; inspect the registry and retry
563
+ the operation to obtain fresh credentials.
564
+
565
+ Registry assignments are reserved before listeners start and reused unchanged
566
+ across native/WireGuard connections. Disconnect retains that reservation; record
567
+ removal releases it only after transport teardown. Explicit assignments, including
568
+ raw WireGuard reservations, precede dynamic allocation. A failed create cannot
569
+ release an address belonging to a concurrent registry revision or recreated record.
570
+ Decrypted registered-client packets must have a valid IPv4 header and exactly the
571
+ assigned source address. Connection ACLs use the outer transport endpoint;
572
+ destination ACLs use the inner packet destination. Runtime rate-limit adjustments
573
+ apply to the current session only; use registry security settings for reconnects.
574
+
575
+ Socket and bridge forwarding queues retain authenticated session ownership until
576
+ their packet work is destroyed. Socket mode gives each session its own smoltcp
577
+ stack; policy and PROXY identity come from that captured session, never a mutable
578
+ registry lookup by packet source. Its TCP/UDP flows and socket readers are joined
579
+ on revocation, and replies return only to their original owner. Flow incarnations
580
+ also fence late responses when a tuple is reused. UDP flows sharing a destination
581
+ are selected by received source endpoint, and real UDP sockets accept replies only
582
+ from their configured target.
583
+
584
+ Return queues are also generation-owned: cancellation purges queued payloads
585
+ without waiting for a consumer, including WireGuard's shared return queue.
586
+ Dequeued packets retain their destination lifetime, and internal relay packets
587
+ retain their source lifetime too, until I/O finishes or the payload is destroyed.
588
+ An interrupted native ciphertext write retires that destination tunnel rather
589
+ than continuing a partially written frame or an advanced Noise nonce sequence.
590
+ WireGuard returns require a current encryption session. While rekeying without
591
+ one, application packets are dropped and counted; only a handshake is requested,
592
+ so plaintext cannot escape owner cancellation through BoringTun's private queue.
593
+ Queues are bounded (256 per session, 1024 for shared WG returns); full or closed
594
+ queues reject admission. Managed relay uses these same two-owner lifetimes.
595
+
596
+ Socket NAT is bounded to 256 active engines and 1,024 flow incarnations per server
597
+ dispatcher. The per-client default is 128 TCP/UDP flows combined; a registered
598
+ client's `security.maxConnections` overrides that local limit without bypassing
599
+ the global cap. Late task/message references retain capacity until destroyed.
600
+ Protocol and dispatch queues are bounded. This IPv4 userspace build does not
601
+ reassemble fragments and rejects them before creating flows.
602
+
603
+ These guarantees cover daemon-owned work, not traffic already handed to an
604
+ external network or kernel. Managed relay is described below. Transactional cleanup
605
+ and privileged qualification of standalone kernel modes remain outstanding;
606
+ packet fixtures do not prove production network readiness.
607
+
399
608
  ```typescript
400
609
  // Create (generates keys, assigns IP, returns config bundle)
401
610
  const bundle = await server.createClient({ clientId: 'bob-phone' });
@@ -424,6 +633,108 @@ const wgConf = await server.exportClientConfig('bob-phone', 'wireguard');
424
633
  await server.removeClient('bob-phone');
425
634
  ```
426
635
 
636
+ ### Caller-owned managed networks
637
+
638
+ `forwardingMode: 'managed'` is an exclusive IPv4 node relay. The hub creates no
639
+ TUN, NAT, bridge or host routes. Start it with a fixed `subnet` and required
640
+ `managedAuthorityId`, then call `reconcileManagedNetwork(snapshot)`. Cloudly (or
641
+ another trusted controller) owns durable node IDs, keys, control addresses,
642
+ workload prefixes and policy; SmartVPN owns volatile enforcement, not persistence.
643
+ Protect the management IPC endpoint: the authority ID is a binding, not a credential.
644
+
645
+ ```typescript
646
+ import type { IManagedNetworkSnapshot } from '@push.rocks/smartvpn';
647
+
648
+ const snapshot: IManagedNetworkSnapshot = {
649
+ schemaVersion: 1,
650
+ authorityId: 'cloudly',
651
+ revision: 1,
652
+ nodes: [{
653
+ nodeId: 'worker-a',
654
+ publicKey: '<caller-owned-noise-public-key-base64>',
655
+ controlAddress: '10.88.0.2',
656
+ controlPolicyDomain: 'control',
657
+ enabled: true,
658
+ workloadPrefixes: [{ cidr: '10.90.0.0/24', policyDomain: 'task.vc' }],
659
+ }],
660
+ grants: [], // Absent grants deny, including within the same domain.
661
+ };
662
+ const applied = await server.reconcileManagedNetwork(snapshot);
663
+ const status = await server.getManagedNetworkStatus();
664
+ const projection = await server.getManagedNodeProjection('worker-a');
665
+ ```
666
+
667
+ Snapshots are complete replacements. Canonical array order makes exact replay
668
+ idempotent; stale revisions and conflicting equal revisions reject. Revisions
669
+ are positive JSON-safe integers scoped to `lifetimeId`. Every daemon restart
670
+ starts empty and requires the caller to reconcile its current durable state.
671
+ Node keys must be unique canonical/contributory X25519 keys, including across
672
+ Noise and WireGuard. Control addresses are exact usable hosts in the fixed subnet;
673
+ workload prefixes cannot overlap that subnet or any other owned prefix.
674
+
675
+ Grants are explicit `(sourceDomain, destinationDomain)` pairs. They neither imply
676
+ same-domain permission nor reverse/reply permission. Different workload prefixes
677
+ on one node may belong to different domains. Disabled, expired, missing, offline
678
+ or denied destinations drop without host fallback. Traffic that never crosses
679
+ the hub needs local enforcement by the node executor; local workload prefixes
680
+ are never routed back to the hub merely because a grant involves them.
681
+
682
+ Invalid preparation preserves active authority. A valid change quarantines and
683
+ cancels only affected node generations, joins their packet work, prepares WG
684
+ crypto and commits the whole snapshot atomically before acknowledging. Unchanged
685
+ nodes keep their sessions. A post-drain failure leaves `state: 'failed'`, its
686
+ `pendingRevision` and bounded `lastError`; old affected admission stays closed.
687
+ Only the exact pending snapshot can retry. An IPC timeout or disconnected caller
688
+ does not cancel the process-owned operation. Inspect status and replay that exact
689
+ target; never assume a timed-out request did not commit. Separate Unix IPC
690
+ connections can inspect status while apply drains; stdio remains sequential.
691
+
692
+ Standalone create/update/remove/enable/disable/rotate/disconnect/rate-limit and
693
+ raw WG-peer mutations reject in managed mode, including direct WG-loop commands.
694
+ Standalone startup clients, raw peers, DNS, NAT, bridge and destination-routing
695
+ configuration reject before effects. Managed WireGuard requires an explicit
696
+ `serverEndpoint` and starts without peers until reconciliation.
697
+
698
+ Native clients opt in with `managedNetwork: { authorityId: 'cloudly', nodeId: 'worker-a' }`.
699
+ Missing, unexpected or differently bound managed assignments reject before host
700
+ effects; a managed client cannot downgrade to a standalone subnet assignment.
701
+ Native encrypted assignments carry schema, node/authority/lifetime/revision,
702
+ local ownership and remote outgoing routes. Client `getStatus().managedNetwork`
703
+ reports only the live validated assignment and clears on retirement. Native Linux
704
+ TUN setup uses a fresh nonpersistent /32 device with strict exclusive route adds,
705
+ not shell commands or route-conflict suppression. Preflight rejects malformed,
706
+ overlapping, default, local-prefix and active-hub-endpoint capture. The netlink
707
+ owner is polled inline; failure/cancellation drops the device and its owned routes.
708
+ No DNS or default-route mutation is requested; native `forwardingMode: 'testing'`
709
+ has no host effects. Managed TUN requires Linux.
710
+
711
+ The Linux amd64 musl build passed the isolated `serve.zone/testing` VM scenario:
712
+ real WS/Noise transport and TUN workload traffic between nodes connected only to
713
+ the hub, directed allow/deny, reconnect, natural revocation, partial route-conflict
714
+ rollback, empty-authority restart, and child termination. Cleanup removed owned
715
+ namespaces and preserved DNS and IPv4/IPv6 default routes. This qualification
716
+ does not cover WSS/private CAs, IPv6 workload traffic, privileged ARM64 networking,
717
+ standalone kernel modes, or Pallet/Cloudly production integration.
718
+
719
+ WireGuard has no assignment channel. `getManagedNodeProjection(nodeId)` returns
720
+ the applied control `/32`, per-prefix domains, outgoing destinations, incoming
721
+ sources, MTU and optional public WG configuration, never a private key. Client
722
+ WG AllowedIPs is the union of remote outgoing/incoming prefixes plus the hub's
723
+ MTU-feedback gateway `/32`; the hub independently enforces direction. Server WG
724
+ AllowedIPs contains the node's owned source prefixes. Authenticated WG keepalive
725
+ confirmation admits receive-only managed nodes without an inner probe. Projection
726
+ `ready` means applied/admissible configuration, not connectivity, node installation
727
+ or deployment readiness; disabled, absent and quarantined nodes return no config.
728
+ The controller must apply WG projections through its owned node runtime.
729
+
730
+ Bounds are 1,024 nodes, 128 workload prefixes per node, 16,384 owned prefixes,
731
+ 16,384 grants, 128-byte IDs, 1,024 routes per node per direction, and 262,144 total
732
+ projected routes/grants. Managed relay enforces both live negotiated MTUs and
733
+ returns eligible IPv4 fragmentation-needed feedback to the exact source owner.
734
+ The complete [third-party notice index](./third-party-notices.md) covers the locked
735
+ Cargo graph and statically linked compiler/runtime components. Keep its accompanying
736
+ notice files with separately redistributed executables.
737
+
427
738
  ### 📝 WireGuard Config Generation
428
739
 
429
740
  Generate standard `.conf` files for any WireGuard client:
@@ -493,6 +804,7 @@ client.on('exit', ({ code, signal }) => { /* daemon process exited */ });
493
804
  |-------|-----------|---------|
494
805
  | `exit` | Both | `{ code, signal }` — daemon process exited |
495
806
  | `reconnected` | Both | `void` — socket transport reconnected |
807
+ | `stderr` | Both | `string` — daemon diagnostic line, forwarded without automatic logging |
496
808
 
497
809
  For connection state and telemetry, use `getStatus()`, `getStatistics()`, `listClients()`, and `getClientTelemetry()`.
498
810
 
@@ -532,6 +844,8 @@ For connection state and telemetry, use `getStatus()`, `getStatistics()`, `listC
532
844
  | Command | Description |
533
845
  |---------|-------------|
534
846
  | `start` / `stop` | Start/stop the VPN listener |
847
+ | `reconcileManagedNetwork` | Validate, drain and atomically apply a complete managed snapshot |
848
+ | `getManagedNetworkStatus` / `getManagedNodeProjection` | Authority/lifetime/revision status and caller-applied node configuration |
535
849
  | `createClient` | Generate keys, assign IP, return config bundle |
536
850
  | `removeClient` / `getClient` / `listRegisteredClients` | Client registry CRUD |
537
851
  | `updateClient` / `enableClient` / `disableClient` | Modify client state |
@@ -615,6 +929,31 @@ All frames use `[type:1B][length:4B][payload:NB]` with a 64KB max payload:
615
929
 
616
930
  ## Development 🛠️
617
931
 
932
+ ### Native binary selection and build prerequisites
933
+
934
+ The package builds `smartvpn_daemon_linux_amd64_musl` and
935
+ `smartvpn_daemon_linux_arm64_musl` with the pinned Rust 1.95.0 toolchain and
936
+ locked Cargo dependencies. Both targets require genuine musl C toolchains for
937
+ the native `ring` and `mimalloc` dependencies. Put `x86_64-linux-musl-gcc` and
938
+ `aarch64-linux-musl-gcc` on `PATH` before building or testing; GNU libc headers
939
+ are not a substitute. The existing build and test commands remain unchanged.
940
+
941
+ In stdio mode the bridge selects exactly the bundled executable for Linux x64
942
+ or arm64. `SMARTVPN_RUST_BINARY` selects an explicit development executable on
943
+ any supported native build platform. Missing, non-executable, and empty explicit
944
+ selections fail: there is no fallback to old GNU artifacts, the current working
945
+ directory's Rust builds, platform packages, or a daemon found on `PATH`.
946
+ Other platforms must provide that explicit executable. Socket transport connects
947
+ to an independently managed daemon and does not require a local binary.
948
+
949
+ For an unprivileged build host, musl's upstream `musl-gcc` wrappers can provide
950
+ the C toolchains using existing native and cross GCC compilers. Build musl 1.2.5
951
+ in separate disposable prefixes for each architecture, with `--disable-shared`
952
+ and an explicitly prefix-local `--syslibdir`. Use the two musl security patches
953
+ 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).
954
+ Expose the generated wrappers under the target-specific names above; never
955
+ install a replacement libc or dynamic linker into the host's system directories.
956
+
618
957
  ```bash
619
958
  # Install dependencies
620
959
  pnpm install
@@ -674,7 +1013,7 @@ smartvpn/
674
1013
  │ └── reconnect.rs # Exponential backoff + session tokens
675
1014
  ├── test/ # Test files
676
1015
  ├── dist_ts/ # Compiled TypeScript
677
- └── dist_rust/ # Cross-compiled binaries (linux amd64 + arm64)
1016
+ └── dist_rust/ # Static musl binaries (Linux amd64 + arm64)
678
1017
  ```
679
1018
 
680
1019
  ## License and Legal Information
@@ -0,0 +1,81 @@
1
+ # Third-party notices
2
+
3
+ These notices accompany SmartVPN's static Linux amd64 and arm64 musl executables.
4
+ SmartVPN's own code is MIT-licensed under [license.md](./license.md).
5
+
6
+ ## Cargo dependencies
7
+
8
+ [Cargo dependency notices](./notices/cargo-dependencies.html) reproduce the selected
9
+ upstream license and copyright texts for 270 third-party packages. Eight additional
10
+ locked, feature-inactive packages are covered by these supplemental MIT notices:
11
+
12
+ - [aho-corasick 1.1.4](./notices/aho-corasick-license.txt), from the published crate's `LICENSE-MIT`.
13
+ - [defmt 0.3.100 and 1.0.1, defmt-macros 1.0.1, defmt-parser 1.0.0](./notices/defmt-license.txt),
14
+ from their shared upstream `LICENSE-MIT`. The respective source commits are
15
+ `f6decae6e09d1216aff052aa0733a6b5e632a100`, `8459657ef6df5e6f3386512744110caef5f0612a`
16
+ (also macros), and `4a8cdb44891ed57b8ff5a023b6bec7137c48708f` in
17
+ [knurling-rs/defmt](https://github.com/knurling-rs/defmt).
18
+ All three source files have SHA-256 `2710a622a896bba67356913d4d0492cab5465f61b2ecce6d880aeb483834fb50`;
19
+ the copy adds only a final newline.
20
+ - [proc-macro-error2 2.0.1 and proc-macro-error-attr2 2.0.0](./notices/proc-macro-error2-license.txt),
21
+ from their identical published `LICENSE-MIT` files.
22
+ - [valuable 0.1.1](./notices/valuable-license.txt), from
23
+ [its pinned upstream LICENSE](https://github.com/tokio-rs/valuable/blob/9efc29b6e58cef28f6566a47aa7e142a55fead77/LICENSE).
24
+
25
+ Together these cover all 278 locked registry packages, conservatively including
26
+ build-only and target-inactive dependencies. Listing a package does not mean it
27
+ is linked into either Linux executable. MIT is preferred where offered as an
28
+ alternative; other selected terms include Apache-2.0, BSD, ISC, Unicode-3.0,
29
+ CDLA-Permissive-2.0 and the existing tun 0.7.22 dependency's WTFPL.
30
+ The exact package versions, declared license expressions and reviewed file hashes
31
+ are recorded in [inventory.json](./notices/inventory.json).
32
+
33
+ The [additional MIT source attributions](./notices/mit-source-attributions.txt)
34
+ retain cesu8 1.1.0's Rust Project/Eric Kidd source headers and siphasher 1.0.2's
35
+ Rust Project/Frank Denis `COPYING` attribution, together with the selected MIT terms.
36
+ cesu8's historical Rust copyright inventory is also retained conservatively in
37
+ the generated notices. Combined upstream license files remain intact; additional
38
+ terms concerning unlinked components are not a claim that those components ship.
39
+ The earlier [Linux route backend notices](./assets/third-party-licenses.md) remain
40
+ as supplementary exact upstream attribution, not the complete distribution index.
41
+
42
+ ## Compiler and static runtime
43
+
44
+ `rust-toolchain.toml` pins Rust 1.95.0 and both musl targets. Components supplied
45
+ by that compiler are outside Cargo.lock and have separate notices:
46
+
47
+ - [Rust standard-library inventory](./notices/rust-standard-library.html), from the
48
+ official toolchain's `share/doc/rust/COPYRIGHT-library.html`. Its source SHA-256
49
+ is `90567e2718bf7fd65a71a3a43c5596488e80e5f51ed02bfea6fec54458b5f3d1`;
50
+ the copy adds only a final newline. This upstream inventory covers other targets too.
51
+ - [musl copyright](./notices/musl-copyright.txt), from
52
+ [musl 1.2.5](https://git.musl-libc.org/cgit/musl/tree/COPYRIGHT?h=v1.2.5).
53
+ The C build toolchains use musl commit `0784374d561435f7c787a555aeab8ede699ed298`
54
+ with the two security patches selected by
55
+ [Rust 1.95.0's build recipe](https://github.com/rust-lang/rust/blob/1.95.0/src/ci/docker/scripts/musl-toolchain.sh).
56
+ The final executables link Rust's self-contained musl CRT/libraries.
57
+ - [Compiler-builtins license](./notices/compiler-builtins-license.txt), from
58
+ [Rust 1.95.0](https://github.com/rust-lang/rust/blob/1.95.0/library/compiler-builtins/LICENSE.txt),
59
+ retaining MIT and Apache-2.0 WITH LLVM-exception terms.
60
+ - [LLVM libunwind license](./notices/llvm-libunwind-license.txt), from
61
+ [Rust's pinned LLVM revision](https://github.com/rust-lang/llvm-project/blob/1cb4e3833c1919c2e6fb579a23ac0e2b22587b7e/libunwind/LICENSE.TXT),
62
+ including its additional third-party terms.
63
+
64
+ The TypeScript dependency graph is installed independently through the npm
65
+ manifest; its packages are not bundled into these Rust executables.
66
+
67
+ ## Reproduction and review
68
+
69
+ From the repository root, with cargo-about 0.9.1:
70
+
71
+ ```sh
72
+ cargo about generate --manifest-path rust/Cargo.toml --config rust/about.toml --locked --fail --output-file notices/cargo-dependencies.html rust/about.hbs
73
+ ```
74
+
75
+ The configuration selects checksum-verified upstream notices when published
76
+ crates omit or misname them. Network access is required for pinned Git sources.
77
+ After a Cargo graph, compiler, or notice-input change, regenerate and review the
78
+ notices, refresh the inventory, and rerun distribution review. Tests reject stale
79
+ hashes, incomplete graph coverage, generic copyright placeholders and missing npm
80
+ notice paths. Keep this index, `license.md`, `assets/third-party-licenses.md` and
81
+ the entire `notices/` directory beside any separately distributed executable.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartvpn',
6
- version: '1.22.0',
6
+ version: '2.1.0',
7
7
  description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
8
8
  }