@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.
- package/assets/third-party-licenses.md +157 -0
- package/dist_rust/{smartvpn_daemon_linux_amd64 → smartvpn_daemon_linux_amd64_musl} +0 -0
- package/dist_rust/smartvpn_daemon_linux_amd64_musl.tsrust-build.json +14 -0
- package/dist_rust/{smartvpn_daemon_linux_arm64 → smartvpn_daemon_linux_arm64_musl} +0 -0
- package/dist_rust/smartvpn_daemon_linux_arm64_musl.tsrust-build.json +14 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/smartvpn.classes.vpnbridge.d.ts +4 -3
- package/dist_ts/smartvpn.classes.vpnbridge.js +27 -47
- package/dist_ts/smartvpn.classes.vpnclient.d.ts +5 -4
- package/dist_ts/smartvpn.classes.vpnclient.js +10 -5
- package/dist_ts/smartvpn.classes.vpnconfig.d.ts +1 -0
- package/dist_ts/smartvpn.classes.vpnconfig.js +52 -14
- package/dist_ts/smartvpn.classes.vpnserver.d.ts +17 -4
- package/dist_ts/smartvpn.classes.vpnserver.js +80 -40
- package/dist_ts/smartvpn.interfaces.d.ts +155 -16
- package/dist_ts/smartvpn.paths.d.ts +2 -0
- package/dist_ts/smartvpn.paths.js +14 -1
- package/notices/aho-corasick-license.txt +21 -0
- package/notices/cargo-dependencies.html +5202 -0
- package/notices/compiler-builtins-license.txt +275 -0
- package/notices/defmt-license.txt +25 -0
- package/notices/inventory.json +1427 -0
- package/notices/llvm-libunwind-license.txt +311 -0
- package/notices/mit-source-attributions.txt +31 -0
- package/notices/musl-copyright.txt +193 -0
- package/notices/proc-macro-error2-license.txt +21 -0
- package/notices/rust-standard-library.html +8266 -0
- package/notices/valuable-license.txt +25 -0
- package/package.json +15 -7
- package/readme.md +344 -5
- package/third-party-notices.md +81 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/smartvpn.classes.vpnbridge.ts +27 -52
- package/ts/smartvpn.classes.vpnclient.ts +10 -5
- package/ts/smartvpn.classes.vpnconfig.ts +49 -13
- package/ts/smartvpn.classes.vpnserver.ts +94 -40
- package/ts/smartvpn.interfaces.ts +159 -16
- package/ts/smartvpn.paths.ts +19 -0
- package/readme.hints.md +0 -8
- 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.
|
|
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": "^
|
|
28
|
+
"@push.rocks/smartrust": "^2.0.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@git.zone/tsbuild": "^4.4.
|
|
32
|
-
"@git.zone/tsrun": "^2.0.
|
|
33
|
-
"@git.zone/tsrust": "^1.
|
|
34
|
-
"@git.zone/tstest": "^
|
|
35
|
-
"@types/node": "^
|
|
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
|
-
- ⚡ **
|
|
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
|
|
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
|
|
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**:
|
|
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/ #
|
|
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.
|