@push.rocks/smartvpn 1.7.0 → 1.9.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/dist_rust/smartvpn_daemon_linux_amd64 +0 -0
- package/dist_rust/smartvpn_daemon_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/smartvpn.classes.vpnconfig.js +22 -1
- package/dist_ts/smartvpn.classes.vpnserver.d.ts +42 -1
- package/dist_ts/smartvpn.classes.vpnserver.js +65 -1
- package/dist_ts/smartvpn.interfaces.d.ts +152 -1
- package/package.json +1 -1
- package/readme.md +269 -768
- package/readme.plan.md +253 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/smartvpn.classes.vpnconfig.ts +21 -0
- package/ts/smartvpn.classes.vpnserver.ts +77 -0
- package/ts/smartvpn.interfaces.ts +109 -1
package/readme.md
CHANGED
|
@@ -1,893 +1,394 @@
|
|
|
1
1
|
# @push.rocks/smartvpn
|
|
2
2
|
|
|
3
|
-
A high-performance VPN with a **TypeScript control plane** and a **Rust data plane daemon**.
|
|
3
|
+
A high-performance VPN solution with a **TypeScript control plane** and a **Rust data plane daemon**. Enterprise-ready client authentication, triple transport support (WebSocket + QUIC + WireGuard), and a typed hub API for managing clients from code.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
🔐 **Noise IK** mutual authentication — per-client X25519 keypairs, server-side registry
|
|
6
6
|
🚀 **Triple transport**: WebSocket (Cloudflare-friendly), raw **QUIC** (datagrams), and **WireGuard** (standard protocol)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
🛡️ **ACL engine** — deny-overrides-allow IP filtering, aligned with SmartProxy conventions
|
|
8
|
+
🔀 **PROXY protocol v2** — real client IPs behind reverse proxies (HAProxy, SmartProxy, Cloudflare Spectrum)
|
|
9
|
+
📊 **Adaptive QoS**: per-client rate limiting, priority queues, connection quality tracking
|
|
10
|
+
🔄 **Hub API**: one `createClient()` call generates keys, assigns IP, returns both SmartVPN + WireGuard configs
|
|
11
|
+
📡 **Real-time telemetry**: RTT, jitter, loss ratio, link health — all via typed APIs
|
|
11
12
|
|
|
12
13
|
## Issue Reporting and Security
|
|
13
14
|
|
|
14
15
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
15
16
|
|
|
16
|
-
## Install
|
|
17
|
+
## Install 📦
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
20
|
pnpm install @push.rocks/smartvpn
|
|
21
|
+
# or
|
|
22
|
+
npm install @push.rocks/smartvpn
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
The package ships with pre-compiled Rust binaries for **linux/amd64** and **linux/arm64**. No Rust toolchain required at runtime.
|
|
26
|
+
|
|
27
|
+
## Architecture 🏗️
|
|
23
28
|
|
|
24
29
|
```
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
│
|
|
28
|
-
│
|
|
29
|
-
│
|
|
30
|
-
│
|
|
31
|
-
│ │
|
|
32
|
-
│
|
|
33
|
-
|
|
34
|
-
└──────────────────────────┘ │ ├─ codec (binary framing) │
|
|
35
|
-
│ ├─ keepalive (adaptive state FSM) │
|
|
36
|
-
│ ├─ telemetry (RTT/jitter/loss) │
|
|
37
|
-
│ ├─ qos (classify + priority Q) │
|
|
38
|
-
│ ├─ ratelimit (token bucket) │
|
|
39
|
-
│ ├─ mtu (overhead calc + ICMP) │
|
|
40
|
-
│ ├─ tunnel (TUN device) │
|
|
41
|
-
│ ├─ network (NAT/IP pool) │
|
|
42
|
-
│ └─ reconnect (exp. backoff) │
|
|
43
|
-
└────────────────────────────────────┘
|
|
30
|
+
┌──────────────────────────────┐ JSON-lines IPC ┌───────────────────────────────┐
|
|
31
|
+
│ TypeScript Control Plane │ ◄─────────────────────► │ Rust Data Plane Daemon │
|
|
32
|
+
│ │ stdio or Unix sock │ │
|
|
33
|
+
│ VpnServer / VpnClient │ │ Noise IK handshake │
|
|
34
|
+
│ Typed IPC commands │ │ XChaCha20-Poly1305 │
|
|
35
|
+
│ Config validation │ │ WS + QUIC + WireGuard │
|
|
36
|
+
│ Hub: client management │ │ TUN device, IP pool, NAT │
|
|
37
|
+
│ WireGuard .conf generation │ │ Rate limiting, ACLs, QoS │
|
|
38
|
+
└──────────────────────────────┘ └───────────────────────────────┘
|
|
44
39
|
```
|
|
45
40
|
|
|
46
|
-
**
|
|
47
|
-
|
|
48
|
-
| Decision | Choice | Why |
|
|
49
|
-
|----------|--------|-----|
|
|
50
|
-
| Transport | WebSocket + QUIC + WireGuard | WS works through Cloudflare; QUIC gives low latency + datagrams; WG for standard protocol interop |
|
|
51
|
-
| Auto-transport | QUIC first, WS fallback | Best performance when QUIC is available, graceful degradation when it's not |
|
|
52
|
-
| WireGuard | Userspace via `boringtun` | No kernel module needed, runs on any platform, full peer management via IPC |
|
|
53
|
-
| Encryption | Noise NK + XChaCha20-Poly1305 | Strong forward secrecy, large nonce space (no counter sync needed) |
|
|
54
|
-
| QUIC auth | Certificate hash pinning | WireGuard-style trust model — no CA needed, just pin the server cert hash |
|
|
55
|
-
| Keepalive | Adaptive app-level pings | Cloudflare drops WS pings; interval adapts to link health (10–60s) |
|
|
56
|
-
| QoS | Packet classification + priority queues | DNS/SSH/ICMP always drain first; bulk flows get deprioritized |
|
|
57
|
-
| Rate limiting | Per-client token bucket | Byte-granular, dynamically reconfigurable via IPC |
|
|
58
|
-
| IPC | JSON lines over stdio / Unix socket | `stdio` for dev, `socket` for production (daemon stays alive) |
|
|
59
|
-
| Binary protocol | `[type:1B][length:4B][payload:NB]` | Minimal overhead, easy to parse at wire speed |
|
|
41
|
+
**Split-plane design** — TypeScript handles orchestration, config, and DX; Rust handles every hot-path byte with zero-copy async I/O (tokio, mimalloc).
|
|
60
42
|
|
|
61
|
-
##
|
|
43
|
+
## Quick Start 🚀
|
|
62
44
|
|
|
63
|
-
### VPN
|
|
45
|
+
### 1. Start a VPN Server (Hub)
|
|
64
46
|
|
|
65
47
|
```typescript
|
|
66
|
-
import {
|
|
67
|
-
|
|
68
|
-
const client = new VpnClient({
|
|
69
|
-
transport: { transport: 'stdio' },
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
await client.start();
|
|
48
|
+
import { VpnServer } from '@push.rocks/smartvpn';
|
|
73
49
|
|
|
74
|
-
const {
|
|
75
|
-
|
|
76
|
-
|
|
50
|
+
const server = new VpnServer({ transport: { transport: 'stdio' } });
|
|
51
|
+
await server.start({
|
|
52
|
+
listenAddr: '0.0.0.0:443',
|
|
53
|
+
privateKey: '<server-noise-private-key-base64>',
|
|
54
|
+
publicKey: '<server-noise-public-key-base64>',
|
|
55
|
+
subnet: '10.8.0.0/24',
|
|
56
|
+
transportMode: 'both', // WebSocket + QUIC simultaneously
|
|
57
|
+
enableNat: true,
|
|
77
58
|
dns: ['1.1.1.1', '8.8.8.8'],
|
|
78
|
-
mtu: 1420,
|
|
79
|
-
keepaliveIntervalSecs: 30,
|
|
80
59
|
});
|
|
60
|
+
```
|
|
81
61
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Connection quality (adaptive keepalive + telemetry)
|
|
85
|
-
const quality = await client.getConnectionQuality();
|
|
86
|
-
console.log(quality);
|
|
87
|
-
// {
|
|
88
|
-
// srttMs: 42.5, jitterMs: 3.2, minRttMs: 38.0, maxRttMs: 67.0,
|
|
89
|
-
// lossRatio: 0.0, consecutiveTimeouts: 0,
|
|
90
|
-
// linkHealth: 'healthy', currentKeepaliveIntervalSecs: 60
|
|
91
|
-
// }
|
|
92
|
-
|
|
93
|
-
// MTU info
|
|
94
|
-
const mtu = await client.getMtuInfo();
|
|
95
|
-
console.log(mtu);
|
|
96
|
-
// { tunMtu: 1420, effectiveMtu: 1421, linkMtu: 1500, overheadBytes: 79, ... }
|
|
62
|
+
### 2. Create a Client (One Call = Everything)
|
|
97
63
|
|
|
98
|
-
|
|
99
|
-
const
|
|
64
|
+
```typescript
|
|
65
|
+
const bundle = await server.createClient({
|
|
66
|
+
clientId: 'alice-laptop',
|
|
67
|
+
tags: ['engineering'],
|
|
68
|
+
security: {
|
|
69
|
+
destinationAllowList: ['10.0.0.0/8'], // can only reach internal network
|
|
70
|
+
destinationBlockList: ['10.0.0.99'], // except this host
|
|
71
|
+
rateLimit: { bytesPerSec: 10_000_000, burstBytes: 20_000_000 },
|
|
72
|
+
},
|
|
73
|
+
});
|
|
100
74
|
|
|
101
|
-
|
|
102
|
-
|
|
75
|
+
// bundle.smartvpnConfig → typed IVpnClientConfig, ready to use
|
|
76
|
+
// bundle.wireguardConfig → standard WireGuard .conf string
|
|
77
|
+
// bundle.secrets → { noisePrivateKey, wgPrivateKey } — shown ONCE
|
|
103
78
|
```
|
|
104
79
|
|
|
105
|
-
###
|
|
80
|
+
### 3. Connect a Client
|
|
106
81
|
|
|
107
82
|
```typescript
|
|
108
83
|
import { VpnClient } from '@push.rocks/smartvpn';
|
|
109
84
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
transport: { transport: 'stdio' },
|
|
113
|
-
});
|
|
85
|
+
const client = new VpnClient({ transport: { transport: 'stdio' } });
|
|
86
|
+
await client.start();
|
|
114
87
|
|
|
115
|
-
await
|
|
88
|
+
const { assignedIp } = await client.connect(bundle.smartvpnConfig);
|
|
89
|
+
console.log(`Connected! VPN IP: ${assignedIp}`);
|
|
90
|
+
```
|
|
116
91
|
|
|
117
|
-
|
|
118
|
-
serverUrl: 'vpn.example.com:443',
|
|
119
|
-
serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY',
|
|
120
|
-
transport: 'quic',
|
|
121
|
-
serverCertHash: 'BASE64_SHA256_CERT_HASH', // printed by server on startup
|
|
122
|
-
});
|
|
92
|
+
## Features ✨
|
|
123
93
|
|
|
124
|
-
|
|
125
|
-
const autoClient = new VpnClient({
|
|
126
|
-
transport: { transport: 'stdio' },
|
|
127
|
-
});
|
|
94
|
+
### 🔐 Enterprise Authentication (Noise IK)
|
|
128
95
|
|
|
129
|
-
|
|
96
|
+
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.
|
|
130
97
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
transport: 'auto', // default — QUIC first, then WS
|
|
135
|
-
});
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### VPN Client with WireGuard
|
|
98
|
+
- Per-client X25519 keypair generated server-side
|
|
99
|
+
- Client registry with enable/disable, expiry, tags
|
|
100
|
+
- Key rotation with `rotateClientKey()` — generates new keys, returns fresh config bundle, disconnects old session
|
|
139
101
|
|
|
140
|
-
|
|
141
|
-
import { VpnClient } from '@push.rocks/smartvpn';
|
|
102
|
+
### 🌐 Triple Transport
|
|
142
103
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
104
|
+
| Transport | Protocol | Best For |
|
|
105
|
+
|-----------|----------|----------|
|
|
106
|
+
| **WebSocket** | TLS over TCP | Firewall-friendly, Cloudflare compatible |
|
|
107
|
+
| **QUIC** | UDP (via quinn) | Low latency, datagram support for IP packets |
|
|
108
|
+
| **WireGuard** | UDP (via boringtun) | Standard WG clients (iOS, Android, wg-quick) |
|
|
146
109
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const { assignedIp } = await wgClient.connect({
|
|
150
|
-
serverPublicKey: 'BASE64_SERVER_WG_PUBLIC_KEY',
|
|
151
|
-
serverUrl: '', // not used for WireGuard
|
|
152
|
-
transport: 'wireguard',
|
|
153
|
-
wgPrivateKey: 'BASE64_CLIENT_PRIVATE_KEY',
|
|
154
|
-
wgAddress: '10.8.0.2',
|
|
155
|
-
wgAddressPrefix: 24,
|
|
156
|
-
wgEndpoint: 'vpn.example.com:51820',
|
|
157
|
-
wgAllowedIps: ['0.0.0.0/0'], // route all traffic
|
|
158
|
-
wgPersistentKeepalive: 25,
|
|
159
|
-
wgPresharedKey: 'OPTIONAL_PSK', // optional extra layer
|
|
160
|
-
dns: ['1.1.1.1'],
|
|
161
|
-
mtu: 1420,
|
|
162
|
-
});
|
|
110
|
+
The server can run **all three simultaneously** with `transportMode: 'both'` (WS + QUIC) or `'wireguard'`. Clients auto-negotiate with `transport: 'auto'` (tries QUIC first, falls back to WS).
|
|
163
111
|
|
|
164
|
-
|
|
165
|
-
await wgClient.disconnect();
|
|
166
|
-
wgClient.stop();
|
|
167
|
-
```
|
|
112
|
+
### 🛡️ ACL Engine (SmartProxy-Aligned)
|
|
168
113
|
|
|
169
|
-
|
|
114
|
+
Security policies per client, using the same `ipAllowList` / `ipBlockList` naming convention as `@push.rocks/smartproxy`:
|
|
170
115
|
|
|
171
116
|
```typescript
|
|
172
|
-
|
|
117
|
+
security: {
|
|
118
|
+
ipAllowList: ['192.168.1.0/24'], // source IPs allowed to connect
|
|
119
|
+
ipBlockList: ['192.168.1.100'], // deny overrides allow
|
|
120
|
+
destinationAllowList: ['10.0.0.0/8'], // VPN destinations permitted
|
|
121
|
+
destinationBlockList: ['10.0.0.99'], // deny overrides allow
|
|
122
|
+
maxConnections: 5,
|
|
123
|
+
rateLimit: { bytesPerSec: 1_000_000, burstBytes: 2_000_000 },
|
|
124
|
+
}
|
|
125
|
+
```
|
|
173
126
|
|
|
174
|
-
|
|
175
|
-
transport: { transport: 'stdio' },
|
|
176
|
-
});
|
|
127
|
+
Supports exact IPs, CIDR, wildcards (`192.168.1.*`), and ranges (`1.1.1.1-1.1.1.100`).
|
|
177
128
|
|
|
178
|
-
|
|
179
|
-
await server.start();
|
|
180
|
-
const keypair = await server.generateKeypair();
|
|
129
|
+
### 🔀 PROXY Protocol v2
|
|
181
130
|
|
|
182
|
-
|
|
131
|
+
When the VPN server sits behind a reverse proxy, enable PROXY protocol v2 to receive the **real client IP** instead of the proxy's address. This makes `ipAllowList` / `ipBlockList` ACLs work correctly through load balancers.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
183
134
|
await server.start({
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
subnet: '10.8.0.0/24',
|
|
188
|
-
dns: ['1.1.1.1'],
|
|
189
|
-
mtu: 1420,
|
|
190
|
-
enableNat: true,
|
|
191
|
-
// Transport mode: 'websocket', 'quic', 'both', or 'wireguard'
|
|
192
|
-
transportMode: 'both',
|
|
193
|
-
// Optional: separate QUIC listen address
|
|
194
|
-
quicListenAddr: '0.0.0.0:4433',
|
|
195
|
-
// Optional: QUIC idle timeout
|
|
196
|
-
quicIdleTimeoutSecs: 30,
|
|
197
|
-
// Optional: default rate limit for all new clients
|
|
198
|
-
defaultRateLimitBytesPerSec: 10_000_000, // 10 MB/s
|
|
199
|
-
defaultBurstBytes: 20_000_000, // 20 MB burst
|
|
135
|
+
// ... other config ...
|
|
136
|
+
proxyProtocol: true, // parse PP v2 headers on WS connections
|
|
137
|
+
connectionIpBlockList: ['198.51.100.0/24'], // server-wide block list (pre-handshake)
|
|
200
138
|
});
|
|
139
|
+
```
|
|
201
140
|
|
|
202
|
-
|
|
203
|
-
const clients = await server.listClients();
|
|
141
|
+
**Two-phase ACL with real IPs:**
|
|
204
142
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
143
|
+
| Phase | When | What Happens |
|
|
144
|
+
|-------|------|-------------|
|
|
145
|
+
| **Pre-handshake** | After TCP accept | Server-level `connectionIpBlockList` rejects known-bad IPs — zero crypto cost |
|
|
146
|
+
| **Post-handshake** | After Noise IK identifies client | Per-client `ipAllowList` / `ipBlockList` checked against real source IP |
|
|
208
147
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
// rateLimitBytesPerSec, burstBytes
|
|
216
|
-
// }
|
|
148
|
+
- Parses the PP v2 binary header from raw TCP before WebSocket upgrade
|
|
149
|
+
- 5-second timeout protects against stalling attacks
|
|
150
|
+
- LOCAL command (proxy health checks) handled gracefully
|
|
151
|
+
- IPv4 and IPv6 addresses supported
|
|
152
|
+
- `remoteAddr` field on `IVpnClientInfo` exposes the real client IP for monitoring
|
|
153
|
+
- **Security**: must be `false` (default) when accepting direct connections — only enable behind a trusted proxy
|
|
217
154
|
|
|
218
|
-
|
|
219
|
-
await server.disconnectClient('client-id');
|
|
155
|
+
### 📊 Telemetry & QoS
|
|
220
156
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
157
|
+
- **Connection quality**: Smoothed RTT, jitter, min/max RTT, loss ratio, link health (`healthy` / `degraded` / `critical`)
|
|
158
|
+
- **Adaptive keepalives**: Interval adjusts based on link health (60s → 30s → 10s)
|
|
159
|
+
- **Per-client rate limiting**: Token bucket with configurable bytes/sec and burst
|
|
160
|
+
- **Dead-peer detection**: 180s inactivity timeout
|
|
161
|
+
- **MTU management**: Automatic overhead calculation (IP+TCP+WS+Noise = 79 bytes)
|
|
224
162
|
|
|
225
|
-
###
|
|
163
|
+
### 🔄 Hub Client Management
|
|
226
164
|
|
|
227
|
-
|
|
228
|
-
import { VpnServer } from '@push.rocks/smartvpn';
|
|
165
|
+
The server acts as a **hub** — one API to manage all clients:
|
|
229
166
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
});
|
|
167
|
+
```typescript
|
|
168
|
+
// Create (generates keys, assigns IP, returns config bundle)
|
|
169
|
+
const bundle = await server.createClient({ clientId: 'bob-phone' });
|
|
233
170
|
|
|
234
|
-
//
|
|
235
|
-
await
|
|
236
|
-
const
|
|
237
|
-
console.log(`Server public key: ${keypair.publicKey}`);
|
|
171
|
+
// Read
|
|
172
|
+
const entry = await server.getClient('bob-phone');
|
|
173
|
+
const all = await server.listRegisteredClients();
|
|
238
174
|
|
|
239
|
-
//
|
|
240
|
-
await
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
publicKey: keypair.publicKey,
|
|
244
|
-
subnet: '10.8.0.0/24',
|
|
245
|
-
transportMode: 'wireguard',
|
|
246
|
-
wgListenPort: 51820,
|
|
247
|
-
wgPeers: [
|
|
248
|
-
{
|
|
249
|
-
publicKey: 'CLIENT_PUBLIC_KEY_BASE64',
|
|
250
|
-
allowedIps: ['10.8.0.2/32'],
|
|
251
|
-
persistentKeepalive: 25,
|
|
252
|
-
},
|
|
253
|
-
],
|
|
254
|
-
enableNat: true,
|
|
255
|
-
dns: ['1.1.1.1'],
|
|
256
|
-
mtu: 1420,
|
|
175
|
+
// Update (ACLs, tags, description, rate limits...)
|
|
176
|
+
await server.updateClient('bob-phone', {
|
|
177
|
+
security: { destinationAllowList: ['0.0.0.0/0'] },
|
|
178
|
+
tags: ['mobile', 'field-ops'],
|
|
257
179
|
});
|
|
258
180
|
|
|
259
|
-
//
|
|
260
|
-
await
|
|
261
|
-
|
|
262
|
-
allowedIps: ['10.8.0.3/32'],
|
|
263
|
-
persistentKeepalive: 25,
|
|
264
|
-
});
|
|
181
|
+
// Enable / Disable
|
|
182
|
+
await server.disableClient('bob-phone'); // disconnects + blocks reconnection
|
|
183
|
+
await server.enableClient('bob-phone');
|
|
265
184
|
|
|
266
|
-
//
|
|
267
|
-
const
|
|
268
|
-
for (const peer of peers) {
|
|
269
|
-
console.log(`${peer.publicKey}: ↑${peer.bytesSent} ↓${peer.bytesReceived}`);
|
|
270
|
-
}
|
|
185
|
+
// Key rotation
|
|
186
|
+
const newBundle = await server.rotateClientKey('bob-phone');
|
|
271
187
|
|
|
272
|
-
//
|
|
273
|
-
await
|
|
188
|
+
// Export config (without secrets)
|
|
189
|
+
const wgConf = await server.exportClientConfig('bob-phone', 'wireguard');
|
|
274
190
|
|
|
275
|
-
|
|
276
|
-
|
|
191
|
+
// Remove
|
|
192
|
+
await server.removeClient('bob-phone');
|
|
277
193
|
```
|
|
278
194
|
|
|
279
|
-
###
|
|
195
|
+
### 📝 WireGuard Config Generation
|
|
280
196
|
|
|
281
|
-
|
|
197
|
+
Generate standard `.conf` files for any WireGuard client:
|
|
282
198
|
|
|
283
199
|
```typescript
|
|
284
200
|
import { WgConfigGenerator } from '@push.rocks/smartvpn';
|
|
285
201
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
privateKey: 'CLIENT_PRIVATE_KEY_BASE64',
|
|
202
|
+
const conf = WgConfigGenerator.generateClientConfig({
|
|
203
|
+
privateKey: '<client-wg-private-key>',
|
|
289
204
|
address: '10.8.0.2/24',
|
|
290
|
-
dns: ['1.1.1.1'
|
|
291
|
-
mtu: 1420,
|
|
205
|
+
dns: ['1.1.1.1'],
|
|
292
206
|
peer: {
|
|
293
|
-
publicKey: '
|
|
207
|
+
publicKey: '<server-wg-public-key>',
|
|
294
208
|
endpoint: 'vpn.example.com:51820',
|
|
295
|
-
allowedIps: ['0.0.0.0/0'
|
|
209
|
+
allowedIps: ['0.0.0.0/0'],
|
|
296
210
|
persistentKeepalive: 25,
|
|
297
|
-
presharedKey: 'OPTIONAL_PSK_BASE64',
|
|
298
|
-
},
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
// Server config (for wg-quick)
|
|
302
|
-
const serverConf = WgConfigGenerator.generateServerConfig({
|
|
303
|
-
privateKey: 'SERVER_PRIVATE_KEY_BASE64',
|
|
304
|
-
address: '10.8.0.1/24',
|
|
305
|
-
listenPort: 51820,
|
|
306
|
-
dns: ['1.1.1.1'],
|
|
307
|
-
mtu: 1420,
|
|
308
|
-
enableNat: true,
|
|
309
|
-
natInterface: 'eth0', // auto-detected if omitted
|
|
310
|
-
peers: [
|
|
311
|
-
{
|
|
312
|
-
publicKey: 'CLIENT_PUBLIC_KEY_BASE64',
|
|
313
|
-
allowedIps: ['10.8.0.2/32'],
|
|
314
|
-
persistentKeepalive: 25,
|
|
315
|
-
},
|
|
316
|
-
],
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// Write to disk
|
|
320
|
-
import * as fs from 'fs';
|
|
321
|
-
fs.writeFileSync('/etc/wireguard/wg0.conf', serverConf);
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
<details>
|
|
325
|
-
<summary>Example output: client .conf</summary>
|
|
326
|
-
|
|
327
|
-
```ini
|
|
328
|
-
[Interface]
|
|
329
|
-
PrivateKey = CLIENT_PRIVATE_KEY_BASE64
|
|
330
|
-
Address = 10.8.0.2/24
|
|
331
|
-
DNS = 1.1.1.1, 8.8.8.8
|
|
332
|
-
MTU = 1420
|
|
333
|
-
|
|
334
|
-
[Peer]
|
|
335
|
-
PublicKey = SERVER_PUBLIC_KEY_BASE64
|
|
336
|
-
PresharedKey = OPTIONAL_PSK_BASE64
|
|
337
|
-
Endpoint = vpn.example.com:51820
|
|
338
|
-
AllowedIPs = 0.0.0.0/0, ::/0
|
|
339
|
-
PersistentKeepalive = 25
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
</details>
|
|
343
|
-
|
|
344
|
-
### Production: Socket Transport
|
|
345
|
-
|
|
346
|
-
In production, the daemon runs as a system service and you connect over a Unix socket:
|
|
347
|
-
|
|
348
|
-
```typescript
|
|
349
|
-
const client = new VpnClient({
|
|
350
|
-
transport: {
|
|
351
|
-
transport: 'socket',
|
|
352
|
-
socketPath: '/var/run/smartvpn.sock',
|
|
353
|
-
autoReconnect: true,
|
|
354
|
-
reconnectBaseDelayMs: 100,
|
|
355
|
-
reconnectMaxDelayMs: 30000,
|
|
356
|
-
maxReconnectAttempts: 10,
|
|
357
211
|
},
|
|
358
212
|
});
|
|
359
|
-
|
|
360
|
-
await client.start(); // connects to existing daemon (does not spawn)
|
|
213
|
+
// → standard WireGuard .conf compatible with wg-quick, iOS, Android
|
|
361
214
|
```
|
|
362
215
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
## 📋 API Reference
|
|
366
|
-
|
|
367
|
-
### `VpnClient`
|
|
368
|
-
|
|
369
|
-
| Method | Returns | Description |
|
|
370
|
-
|--------|---------|-------------|
|
|
371
|
-
| `start()` | `Promise<boolean>` | Start the daemon bridge (spawn or connect) |
|
|
372
|
-
| `connect(config?)` | `Promise<{ assignedIp }>` | Connect to VPN server (WS, QUIC, or WireGuard) |
|
|
373
|
-
| `disconnect()` | `Promise<void>` | Disconnect from VPN |
|
|
374
|
-
| `getStatus()` | `Promise<IVpnStatus>` | Current connection state |
|
|
375
|
-
| `getStatistics()` | `Promise<IVpnStatistics>` | Traffic stats + connection quality |
|
|
376
|
-
| `getConnectionQuality()` | `Promise<IVpnConnectionQuality>` | RTT, jitter, loss, link health |
|
|
377
|
-
| `getMtuInfo()` | `Promise<IVpnMtuInfo>` | MTU info and overhead breakdown |
|
|
378
|
-
| `stop()` | `void` | Kill/close the daemon bridge |
|
|
379
|
-
| `running` | `boolean` | Whether bridge is active |
|
|
380
|
-
|
|
381
|
-
### `VpnServer`
|
|
382
|
-
|
|
383
|
-
| Method | Returns | Description |
|
|
384
|
-
|--------|---------|-------------|
|
|
385
|
-
| `start(config?)` | `Promise<void>` | Start daemon + VPN server |
|
|
386
|
-
| `stopServer()` | `Promise<void>` | Stop the VPN server |
|
|
387
|
-
| `getStatus()` | `Promise<IVpnStatus>` | Server connection state |
|
|
388
|
-
| `getStatistics()` | `Promise<IVpnServerStatistics>` | Server stats (includes client counts) |
|
|
389
|
-
| `listClients()` | `Promise<IVpnClientInfo[]>` | Connected clients with QoS stats |
|
|
390
|
-
| `disconnectClient(id)` | `Promise<void>` | Kick a client |
|
|
391
|
-
| `generateKeypair()` | `Promise<IVpnKeypair>` | Generate Noise NK keypair |
|
|
392
|
-
| `setClientRateLimit(id, rate, burst)` | `Promise<void>` | Set per-client rate limit (bytes/sec) |
|
|
393
|
-
| `removeClientRateLimit(id)` | `Promise<void>` | Remove rate limit (unlimited) |
|
|
394
|
-
| `getClientTelemetry(id)` | `Promise<IVpnClientTelemetry>` | Per-client telemetry + drop stats |
|
|
395
|
-
| `generateWgKeypair()` | `Promise<IVpnKeypair>` | Generate WireGuard X25519 keypair |
|
|
396
|
-
| `addWgPeer(peer)` | `Promise<void>` | Add a WireGuard peer at runtime |
|
|
397
|
-
| `removeWgPeer(publicKey)` | `Promise<void>` | Remove a WireGuard peer by key |
|
|
398
|
-
| `listWgPeers()` | `Promise<IWgPeerInfo[]>` | List WG peers with traffic stats |
|
|
399
|
-
| `stop()` | `void` | Kill/close the daemon bridge |
|
|
400
|
-
|
|
401
|
-
### `VpnConfig`
|
|
402
|
-
|
|
403
|
-
Static utility class for config validation and file I/O:
|
|
404
|
-
|
|
405
|
-
```typescript
|
|
406
|
-
import { VpnConfig } from '@push.rocks/smartvpn';
|
|
407
|
-
|
|
408
|
-
// Validate (throws on invalid)
|
|
409
|
-
VpnConfig.validateClientConfig(config);
|
|
410
|
-
VpnConfig.validateServerConfig(config);
|
|
411
|
-
|
|
412
|
-
// Load/save JSON configs
|
|
413
|
-
const config = await VpnConfig.loadFromFile<IVpnClientConfig>('/etc/smartvpn/client.json');
|
|
414
|
-
await VpnConfig.saveToFile('/etc/smartvpn/client.json', config);
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
Validation covers both smartvpn-native configs and WireGuard configs — base64 key format, CIDR ranges, port ranges, and required fields are all checked.
|
|
418
|
-
|
|
419
|
-
### `WgConfigGenerator`
|
|
420
|
-
|
|
421
|
-
Static generator for standard WireGuard `.conf` files:
|
|
422
|
-
|
|
423
|
-
| Method | Returns | Description |
|
|
424
|
-
|--------|---------|-------------|
|
|
425
|
-
| `generateClientConfig(opts)` | `string` | Generate a `wg-quick` compatible client `.conf` |
|
|
426
|
-
| `generateServerConfig(opts)` | `string` | Generate a `wg-quick` compatible server `.conf` with NAT rules |
|
|
427
|
-
|
|
428
|
-
Output is compatible with `wg-quick`, WireGuard iOS/Android apps, and any standard WireGuard implementation.
|
|
429
|
-
|
|
430
|
-
### `VpnInstaller`
|
|
431
|
-
|
|
432
|
-
Generate system service units for the daemon:
|
|
216
|
+
### 🖥️ System Service Installation
|
|
433
217
|
|
|
434
218
|
```typescript
|
|
435
219
|
import { VpnInstaller } from '@push.rocks/smartvpn';
|
|
436
220
|
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
// Linux (systemd)
|
|
440
|
-
const unit = VpnInstaller.generateSystemdUnit({
|
|
441
|
-
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
|
442
|
-
socketPath: '/var/run/smartvpn.sock',
|
|
221
|
+
const unit = VpnInstaller.generateServiceUnit({
|
|
443
222
|
mode: 'server',
|
|
223
|
+
configPath: '/etc/smartvpn/server.json',
|
|
444
224
|
});
|
|
445
|
-
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
|
449
|
-
socketPath: '/var/run/smartvpn.sock',
|
|
450
|
-
mode: 'client',
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// Auto-detect platform
|
|
454
|
-
const serviceUnit = VpnInstaller.generateServiceUnit({
|
|
455
|
-
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
|
456
|
-
socketPath: '/var/run/smartvpn.sock',
|
|
457
|
-
mode: 'server',
|
|
458
|
-
});
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### Events
|
|
462
|
-
|
|
463
|
-
Both `VpnClient` and `VpnServer` extend `EventEmitter`:
|
|
464
|
-
|
|
465
|
-
```typescript
|
|
466
|
-
client.on('exit', ({ code, signal }) => { /* daemon exited */ });
|
|
467
|
-
client.on('reconnected', () => { /* socket reconnected */ });
|
|
468
|
-
client.on('status', (status) => { /* IVpnStatus update */ });
|
|
469
|
-
client.on('error', (error) => { /* error from daemon */ });
|
|
470
|
-
|
|
471
|
-
server.on('client-connected', (info) => { /* IVpnClientInfo */ });
|
|
472
|
-
server.on('client-disconnected', ({ clientId, reason }) => { /* ... */ });
|
|
473
|
-
server.on('started', () => { /* server listener started */ });
|
|
474
|
-
server.on('stopped', () => { /* server listener stopped */ });
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
## 🌐 Transport Modes
|
|
478
|
-
|
|
479
|
-
smartvpn supports three transport protocols. The smartvpn-native transports (WebSocket + QUIC) share the same encryption, framing, and QoS pipeline. WireGuard mode uses the standard WireGuard protocol for broad interoperability.
|
|
480
|
-
|
|
481
|
-
### WebSocket (default for smartvpn-native)
|
|
482
|
-
|
|
483
|
-
- Works through Cloudflare, reverse proxies, and HTTP load balancers
|
|
484
|
-
- Reliable delivery only (no datagram support)
|
|
485
|
-
- URL format: `wss://host/path` or `ws://host:port/path`
|
|
486
|
-
|
|
487
|
-
### QUIC
|
|
488
|
-
|
|
489
|
-
- Lower latency, built-in multiplexing, 0-RTT connection establishment
|
|
490
|
-
- Supports **unreliable datagrams** for IP packets (with automatic fallback to reliable if oversized)
|
|
491
|
-
- Certificate hash pinning — no CA chain needed, WireGuard-style trust
|
|
492
|
-
- URL format: `host:port`
|
|
493
|
-
- ALPN protocol: `smartvpn`
|
|
494
|
-
|
|
495
|
-
### WireGuard
|
|
496
|
-
|
|
497
|
-
- Standard WireGuard protocol via `boringtun` (userspace, no kernel module)
|
|
498
|
-
- Compatible with **all WireGuard clients** — iOS, Android, macOS, Windows, Linux, routers
|
|
499
|
-
- X25519 key exchange, ChaCha20-Poly1305 encryption
|
|
500
|
-
- Dynamic peer management at runtime (add/remove without restart)
|
|
501
|
-
- Optional preshared keys for post-quantum defense-in-depth
|
|
502
|
-
- Generate `.conf` files for standard clients via `WgConfigGenerator`
|
|
503
|
-
- Default port: `51820/UDP`
|
|
504
|
-
|
|
505
|
-
### Auto-Transport (Recommended for smartvpn-native)
|
|
506
|
-
|
|
507
|
-
The default `transport: 'auto'` mode gives you the best of both worlds:
|
|
508
|
-
|
|
509
|
-
1. Extract `host:port` from the WebSocket URL
|
|
510
|
-
2. Attempt QUIC connection (3-second timeout)
|
|
511
|
-
3. If QUIC fails or times out → fall back to WebSocket
|
|
512
|
-
4. Completely transparent to the application
|
|
513
|
-
|
|
514
|
-
```typescript
|
|
515
|
-
await client.connect({
|
|
516
|
-
serverUrl: 'wss://vpn.example.com/tunnel',
|
|
517
|
-
serverPublicKey: '...',
|
|
518
|
-
transport: 'auto', // default — QUIC first, WS fallback
|
|
519
|
-
});
|
|
225
|
+
// unit.platform → 'linux' | 'macos'
|
|
226
|
+
// unit.content → systemd unit file or launchd plist
|
|
227
|
+
// unit.installPath → /etc/systemd/system/smartvpn-server.service
|
|
520
228
|
```
|
|
521
229
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
230
|
+
## API Reference 📖
|
|
231
|
+
|
|
232
|
+
### Classes
|
|
233
|
+
|
|
234
|
+
| Class | Description |
|
|
235
|
+
|-------|-------------|
|
|
236
|
+
| `VpnServer` | Manages the Rust daemon in server mode. Hub methods for client CRUD. |
|
|
237
|
+
| `VpnClient` | Manages the Rust daemon in client mode. Connect, disconnect, telemetry. |
|
|
238
|
+
| `VpnBridge<T>` | Low-level typed IPC bridge (stdio or Unix socket). |
|
|
239
|
+
| `VpnConfig` | Static config validation and file I/O. |
|
|
240
|
+
| `VpnInstaller` | Generates systemd/launchd service files. |
|
|
241
|
+
| `WgConfigGenerator` | Generates standard WireGuard `.conf` files. |
|
|
242
|
+
|
|
243
|
+
### Key Interfaces
|
|
244
|
+
|
|
245
|
+
| Interface | Purpose |
|
|
246
|
+
|-----------|---------|
|
|
247
|
+
| `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, clients, proxy protocol) |
|
|
248
|
+
| `IVpnClientConfig` | Client configuration (server URL, keys, transport, WG options) |
|
|
249
|
+
| `IClientEntry` | Server-side client definition (ID, keys, security, priority, tags, expiry) |
|
|
250
|
+
| `IClientSecurity` | Per-client ACLs and rate limits (SmartProxy-aligned naming) |
|
|
251
|
+
| `IClientRateLimit` | Rate limiting config (bytesPerSec, burstBytes) |
|
|
252
|
+
| `IClientConfigBundle` | Full config bundle returned by `createClient()` |
|
|
253
|
+
| `IVpnClientInfo` | Connected client info (IP, stats, authenticated key, remote addr) |
|
|
254
|
+
| `IVpnConnectionQuality` | RTT, jitter, loss ratio, link health |
|
|
255
|
+
| `IVpnKeypair` | Base64-encoded public/private key pair |
|
|
256
|
+
|
|
257
|
+
### Server IPC Commands
|
|
258
|
+
|
|
259
|
+
| Command | Description |
|
|
260
|
+
|---------|-------------|
|
|
261
|
+
| `start` / `stop` | Start/stop the VPN listener |
|
|
262
|
+
| `createClient` | Generate keys, assign IP, return config bundle |
|
|
263
|
+
| `removeClient` / `getClient` / `listRegisteredClients` | Client registry CRUD |
|
|
264
|
+
| `updateClient` / `enableClient` / `disableClient` | Modify client state |
|
|
265
|
+
| `rotateClientKey` | Fresh keypairs + new config bundle |
|
|
266
|
+
| `exportClientConfig` | Re-export as SmartVPN config or WireGuard `.conf` |
|
|
267
|
+
| `listClients` / `disconnectClient` | Manage live connections |
|
|
268
|
+
| `setClientRateLimit` / `removeClientRateLimit` | Runtime rate limit adjustments |
|
|
269
|
+
| `getStatus` / `getStatistics` / `getClientTelemetry` | Monitoring |
|
|
270
|
+
| `generateKeypair` / `generateWgKeypair` / `generateClientKeypair` | Key generation |
|
|
271
|
+
| `addWgPeer` / `removeWgPeer` / `listWgPeers` | WireGuard peer management |
|
|
272
|
+
|
|
273
|
+
### Client IPC Commands
|
|
274
|
+
|
|
275
|
+
| Command | Description |
|
|
276
|
+
|---------|-------------|
|
|
277
|
+
| `connect` / `disconnect` | Manage the tunnel |
|
|
278
|
+
| `getStatus` / `getStatistics` | Connection state and traffic stats |
|
|
279
|
+
| `getConnectionQuality` | RTT, jitter, loss, link health |
|
|
280
|
+
| `getMtuInfo` | MTU and overhead details |
|
|
281
|
+
|
|
282
|
+
## Transport Modes 🔀
|
|
283
|
+
|
|
284
|
+
### Server Configuration
|
|
525
285
|
|
|
526
286
|
```typescript
|
|
527
|
-
// WebSocket
|
|
528
|
-
|
|
529
|
-
listenAddr: '0.0.0.0:443', // WebSocket listener
|
|
530
|
-
quicListenAddr: '0.0.0.0:4433', // QUIC listener (optional, defaults to listenAddr)
|
|
531
|
-
transportMode: 'both', // 'websocket' | 'quic' | 'both' | 'wireguard'
|
|
532
|
-
quicIdleTimeoutSecs: 30,
|
|
533
|
-
// ... other config
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
// WireGuard standalone
|
|
537
|
-
await server.start({
|
|
538
|
-
listenAddr: '0.0.0.0:51820',
|
|
539
|
-
transportMode: 'wireguard',
|
|
540
|
-
wgListenPort: 51820,
|
|
541
|
-
wgPeers: [{ publicKey: '...', allowedIps: ['10.8.0.2/32'] }],
|
|
542
|
-
// ... other config
|
|
543
|
-
});
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
When using `'both'` mode, the server logs the QUIC certificate hash on startup — share this with clients for cert pinning.
|
|
287
|
+
// WebSocket only
|
|
288
|
+
{ transportMode: 'websocket', listenAddr: '0.0.0.0:443' }
|
|
547
289
|
|
|
548
|
-
|
|
290
|
+
// QUIC only
|
|
291
|
+
{ transportMode: 'quic', listenAddr: '0.0.0.0:443' }
|
|
549
292
|
|
|
550
|
-
|
|
293
|
+
// Both (WS + QUIC on same or different ports)
|
|
294
|
+
{ transportMode: 'both', listenAddr: '0.0.0.0:443', quicListenAddr: '0.0.0.0:4433' }
|
|
551
295
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
The keepalive system automatically adjusts its interval based on connection quality:
|
|
555
|
-
|
|
556
|
-
| Link Health | Keepalive Interval | Triggered When |
|
|
557
|
-
|-------------|-------------------|----------------|
|
|
558
|
-
| 🟢 Healthy | 60s | Jitter < 30ms, loss < 2%, no timeouts |
|
|
559
|
-
| 🟡 Degraded | 30s | Jitter > 50ms, loss > 5%, or 1+ timeout |
|
|
560
|
-
| 🔴 Critical | 10s | Loss > 20% or 2+ consecutive timeouts |
|
|
561
|
-
|
|
562
|
-
State transitions include hysteresis (3 consecutive good checks to upgrade, 2 to recover) to prevent flapping. Dead peer detection fires after 3 consecutive timeouts in Critical state.
|
|
563
|
-
|
|
564
|
-
### Packet Classification
|
|
565
|
-
|
|
566
|
-
IP packets are classified into three priority levels by inspecting headers (no deep packet inspection):
|
|
567
|
-
|
|
568
|
-
| Priority | Traffic |
|
|
569
|
-
|----------|---------|
|
|
570
|
-
| **High** | ICMP, DNS (port 53), SSH (port 22), small packets (< 128 bytes) |
|
|
571
|
-
| **Normal** | Everything else |
|
|
572
|
-
| **Low** | Bulk flows exceeding 1 MB within a 60s window |
|
|
573
|
-
|
|
574
|
-
Priority channels drain with biased `tokio::select!` — high-priority packets always go first.
|
|
575
|
-
|
|
576
|
-
### Smart Packet Dropping
|
|
577
|
-
|
|
578
|
-
Under backpressure, packets are dropped intelligently:
|
|
579
|
-
|
|
580
|
-
1. **Low** queue full → drop silently
|
|
581
|
-
2. **Normal** queue full → drop
|
|
582
|
-
3. **High** queue full → wait 5ms, then drop as last resort
|
|
583
|
-
|
|
584
|
-
Drop statistics are tracked per priority level and exposed via telemetry.
|
|
585
|
-
|
|
586
|
-
### Per-Client Rate Limiting
|
|
587
|
-
|
|
588
|
-
Token bucket algorithm with byte granularity:
|
|
589
|
-
|
|
590
|
-
```typescript
|
|
591
|
-
// Set: 10 MB/s sustained, 20 MB burst
|
|
592
|
-
await server.setClientRateLimit('client-id', 10_000_000, 20_000_000);
|
|
593
|
-
|
|
594
|
-
// Check drops via telemetry
|
|
595
|
-
const t = await server.getClientTelemetry('client-id');
|
|
596
|
-
console.log(`Dropped: ${t.packetsDropped} packets, ${t.bytesDropped} bytes`);
|
|
597
|
-
|
|
598
|
-
// Remove limit
|
|
599
|
-
await server.removeClientRateLimit('client-id');
|
|
296
|
+
// WireGuard
|
|
297
|
+
{ transportMode: 'wireguard', wgListenPort: 51820, wgPeers: [...] }
|
|
600
298
|
```
|
|
601
299
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
### Path MTU
|
|
605
|
-
|
|
606
|
-
Tunnel overhead is calculated precisely:
|
|
607
|
-
|
|
608
|
-
| Layer | Bytes |
|
|
609
|
-
|-------|-------|
|
|
610
|
-
| IP header | 20 |
|
|
611
|
-
| TCP header (with timestamps) | 32 |
|
|
612
|
-
| WebSocket framing | 6 |
|
|
613
|
-
| VPN frame header | 5 |
|
|
614
|
-
| Noise AEAD tag | 16 |
|
|
615
|
-
| **Total overhead** | **79** |
|
|
616
|
-
|
|
617
|
-
For a standard 1500-byte Ethernet link, effective TUN MTU = **1421 bytes**. The default TUN MTU of 1420 is conservative and correct. Oversized packets get an ICMP "Fragmentation Needed" (Type 3, Code 4) written back into the TUN, so the source TCP adjusts its MSS automatically.
|
|
618
|
-
|
|
619
|
-
## 🔐 Security Model
|
|
620
|
-
|
|
621
|
-
### smartvpn-native (WebSocket / QUIC)
|
|
622
|
-
|
|
623
|
-
The VPN uses a **Noise NK** handshake pattern:
|
|
624
|
-
|
|
625
|
-
1. **NK** = client does **N**ot authenticate, but **K**nows the server's static public key
|
|
626
|
-
2. The client generates an ephemeral keypair, performs `e, es` (DH with server's static key)
|
|
627
|
-
3. Server responds with `e, ee` (DH with both ephemeral keys)
|
|
628
|
-
4. Result: forward-secret transport keys derived from both DH operations
|
|
629
|
-
|
|
630
|
-
Post-handshake, all IP packets are encrypted with **XChaCha20-Poly1305**:
|
|
631
|
-
- 24-byte random nonces (no counter synchronization needed)
|
|
632
|
-
- 16-byte authentication tags
|
|
633
|
-
- Wire format: `[nonce:24B][ciphertext:var][tag:16B]`
|
|
634
|
-
|
|
635
|
-
### WireGuard Mode
|
|
636
|
-
|
|
637
|
-
Uses the standard [Noise IKpsk2](https://www.wireguard.com/protocol/) handshake:
|
|
638
|
-
- **X25519** key exchange (Curve25519 Diffie-Hellman)
|
|
639
|
-
- **ChaCha20-Poly1305** AEAD encryption
|
|
640
|
-
- Optional **preshared keys** for post-quantum defense-in-depth
|
|
641
|
-
- Implemented via `boringtun` — Cloudflare's userspace WireGuard in Rust
|
|
642
|
-
|
|
643
|
-
### QUIC Certificate Pinning
|
|
644
|
-
|
|
645
|
-
When using QUIC transport, the server generates a self-signed TLS certificate (or uses a configured PEM). Instead of relying on a CA chain, clients pin the server's certificate by its **SHA-256 hash** (base64-encoded) — a WireGuard-inspired trust model:
|
|
300
|
+
### Client Configuration
|
|
646
301
|
|
|
647
302
|
```typescript
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// Client pins it:
|
|
652
|
-
await client.connect({
|
|
653
|
-
serverUrl: 'vpn.example.com:443',
|
|
654
|
-
transport: 'quic',
|
|
655
|
-
serverCertHash: '<BASE64_HASH>',
|
|
656
|
-
serverPublicKey: '...',
|
|
657
|
-
});
|
|
658
|
-
```
|
|
303
|
+
// Auto (tries QUIC first, falls back to WS)
|
|
304
|
+
{ transport: 'auto', serverUrl: 'wss://vpn.example.com' }
|
|
659
305
|
|
|
660
|
-
|
|
306
|
+
// Explicit QUIC with certificate pinning
|
|
307
|
+
{ transport: 'quic', serverUrl: '1.2.3.4:4433', serverCertHash: '<sha256-base64>' }
|
|
661
308
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
```
|
|
665
|
-
┌──────────┬──────────┬────────────────────┐
|
|
666
|
-
│ Type (1B)│ Len (4B) │ Payload (variable) │
|
|
667
|
-
└──────────┴──────────┴────────────────────┘
|
|
309
|
+
// WireGuard
|
|
310
|
+
{ transport: 'wireguard', wgPrivateKey: '...', wgEndpoint: 'vpn.example.com:51820', ... }
|
|
668
311
|
```
|
|
669
312
|
|
|
670
|
-
|
|
671
|
-
|------|-------|-------------|
|
|
672
|
-
| `HandshakeInit` | `0x01` | Client → Server handshake |
|
|
673
|
-
| `HandshakeResp` | `0x02` | Server → Client handshake |
|
|
674
|
-
| `IpPacket` | `0x10` | Encrypted IP packet |
|
|
675
|
-
| `Keepalive` | `0x20` | App-level ping (8-byte timestamp payload) |
|
|
676
|
-
| `KeepaliveAck` | `0x21` | App-level pong (echoes timestamp for RTT) |
|
|
677
|
-
| `SessionResume` | `0x30` | Resume a dropped session |
|
|
678
|
-
| `SessionResumeOk` | `0x31` | Resume accepted |
|
|
679
|
-
| `SessionResumeErr` | `0x32` | Resume rejected |
|
|
680
|
-
| `Disconnect` | `0x3F` | Graceful disconnect |
|
|
313
|
+
## Cryptography 🔑
|
|
681
314
|
|
|
682
|
-
|
|
315
|
+
| Layer | Algorithm | Purpose |
|
|
316
|
+
|-------|-----------|---------|
|
|
317
|
+
| **Handshake** | Noise IK (X25519 + ChaChaPoly + BLAKE2s) | Mutual authentication + key exchange |
|
|
318
|
+
| **Transport** | Noise transport state (ChaChaPoly) | All post-handshake data encryption |
|
|
319
|
+
| **Additional** | XChaCha20-Poly1305 | Extended nonce space for data-at-rest |
|
|
320
|
+
| **WireGuard** | X25519 + ChaCha20-Poly1305 (via boringtun) | Standard WireGuard crypto |
|
|
683
321
|
|
|
684
|
-
|
|
322
|
+
## Binary Protocol 📡
|
|
685
323
|
|
|
686
|
-
|
|
324
|
+
All frames use `[type:1B][length:4B][payload:NB]` with a 64KB max payload:
|
|
687
325
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
326
|
+
| Type | Hex | Direction | Description |
|
|
327
|
+
|------|-----|-----------|-------------|
|
|
328
|
+
| HandshakeInit | `0x01` | Client → Server | Noise IK first message |
|
|
329
|
+
| HandshakeResp | `0x02` | Server → Client | Noise IK response |
|
|
330
|
+
| IpPacket | `0x10` | Bidirectional | Encrypted tunnel data |
|
|
331
|
+
| Keepalive | `0x20` | Client → Server | App-level keepalive (not WS ping) |
|
|
332
|
+
| KeepaliveAck | `0x21` | Server → Client | Keepalive response with RTT payload |
|
|
333
|
+
| Disconnect | `0x3F` | Bidirectional | Graceful disconnect |
|
|
692
334
|
|
|
693
|
-
|
|
694
|
-
smartvpn_daemon --management-socket /var/run/smartvpn.sock --mode server
|
|
695
|
-
|
|
696
|
-
# Generate a Noise keypair
|
|
697
|
-
smartvpn_daemon --generate-keypair
|
|
698
|
-
```
|
|
699
|
-
|
|
700
|
-
## 🔧 Building from Source
|
|
335
|
+
## Development 🛠️
|
|
701
336
|
|
|
702
337
|
```bash
|
|
703
338
|
# Install dependencies
|
|
704
339
|
pnpm install
|
|
705
340
|
|
|
706
|
-
# Build TypeScript + cross-compile
|
|
341
|
+
# Build (TypeScript + Rust cross-compile)
|
|
707
342
|
pnpm build
|
|
708
343
|
|
|
709
|
-
#
|
|
710
|
-
cd rust && cargo build
|
|
711
|
-
|
|
712
|
-
# Run all tests (93 Rust + 77 TypeScript)
|
|
713
|
-
cd rust && cargo test
|
|
344
|
+
# Run all tests (79 TS + 129 Rust = 208 tests)
|
|
714
345
|
pnpm test
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
## 📘 TypeScript Interfaces
|
|
718
|
-
|
|
719
|
-
<details>
|
|
720
|
-
<summary>Click to expand full type definitions</summary>
|
|
721
|
-
|
|
722
|
-
```typescript
|
|
723
|
-
// Transport options
|
|
724
|
-
type TVpnTransportOptions =
|
|
725
|
-
| { transport: 'stdio' }
|
|
726
|
-
| {
|
|
727
|
-
transport: 'socket';
|
|
728
|
-
socketPath: string;
|
|
729
|
-
autoReconnect?: boolean;
|
|
730
|
-
reconnectBaseDelayMs?: number;
|
|
731
|
-
reconnectMaxDelayMs?: number;
|
|
732
|
-
maxReconnectAttempts?: number;
|
|
733
|
-
};
|
|
734
|
-
|
|
735
|
-
// Client config
|
|
736
|
-
interface IVpnClientConfig {
|
|
737
|
-
serverUrl: string; // WS: 'wss://host/path' | QUIC: 'host:port'
|
|
738
|
-
serverPublicKey: string; // Base64-encoded Noise static key (or WG public key)
|
|
739
|
-
transport?: 'auto' | 'websocket' | 'quic' | 'wireguard'; // Default: 'auto'
|
|
740
|
-
serverCertHash?: string; // SHA-256 cert hash (base64) for QUIC pinning
|
|
741
|
-
dns?: string[];
|
|
742
|
-
mtu?: number;
|
|
743
|
-
keepaliveIntervalSecs?: number;
|
|
744
|
-
// WireGuard-specific
|
|
745
|
-
wgPrivateKey?: string; // Client private key (base64, X25519)
|
|
746
|
-
wgAddress?: string; // Client TUN address (e.g. 10.8.0.2)
|
|
747
|
-
wgAddressPrefix?: number; // Address prefix length (default: 24)
|
|
748
|
-
wgPresharedKey?: string; // Optional preshared key (base64)
|
|
749
|
-
wgPersistentKeepalive?: number; // Persistent keepalive interval (seconds)
|
|
750
|
-
wgEndpoint?: string; // Server endpoint (host:port)
|
|
751
|
-
wgAllowedIps?: string[]; // Allowed IPs (CIDR strings)
|
|
752
|
-
}
|
|
753
346
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
listenAddr: string;
|
|
757
|
-
privateKey: string;
|
|
758
|
-
publicKey: string;
|
|
759
|
-
subnet: string;
|
|
760
|
-
tlsCert?: string;
|
|
761
|
-
tlsKey?: string;
|
|
762
|
-
dns?: string[];
|
|
763
|
-
mtu?: number;
|
|
764
|
-
keepaliveIntervalSecs?: number;
|
|
765
|
-
enableNat?: boolean;
|
|
766
|
-
transportMode?: 'websocket' | 'quic' | 'both' | 'wireguard';
|
|
767
|
-
quicListenAddr?: string;
|
|
768
|
-
quicIdleTimeoutSecs?: number;
|
|
769
|
-
defaultRateLimitBytesPerSec?: number;
|
|
770
|
-
defaultBurstBytes?: number;
|
|
771
|
-
// WireGuard-specific
|
|
772
|
-
wgListenPort?: number; // UDP port (default: 51820)
|
|
773
|
-
wgPeers?: IWgPeerConfig[]; // Initial peers
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
// WireGuard peer config
|
|
777
|
-
interface IWgPeerConfig {
|
|
778
|
-
publicKey: string; // Peer's X25519 public key (base64)
|
|
779
|
-
presharedKey?: string; // Optional preshared key (base64)
|
|
780
|
-
allowedIps: string[]; // Allowed IP ranges (CIDR)
|
|
781
|
-
endpoint?: string; // Peer endpoint (host:port)
|
|
782
|
-
persistentKeepalive?: number; // Keepalive interval (seconds)
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
// WireGuard peer info (with live stats)
|
|
786
|
-
interface IWgPeerInfo {
|
|
787
|
-
publicKey: string;
|
|
788
|
-
allowedIps: string[];
|
|
789
|
-
endpoint?: string;
|
|
790
|
-
persistentKeepalive?: number;
|
|
791
|
-
bytesSent: number;
|
|
792
|
-
bytesReceived: number;
|
|
793
|
-
packetsSent: number;
|
|
794
|
-
packetsReceived: number;
|
|
795
|
-
lastHandshakeTime?: string;
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// Status
|
|
799
|
-
type TVpnConnectionState = 'disconnected' | 'connecting' | 'handshaking'
|
|
800
|
-
| 'connected' | 'reconnecting' | 'error';
|
|
801
|
-
|
|
802
|
-
interface IVpnStatus {
|
|
803
|
-
state: TVpnConnectionState;
|
|
804
|
-
assignedIp?: string;
|
|
805
|
-
serverAddr?: string;
|
|
806
|
-
connectedSince?: string;
|
|
807
|
-
lastError?: string;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
// Statistics
|
|
811
|
-
interface IVpnStatistics {
|
|
812
|
-
bytesSent: number;
|
|
813
|
-
bytesReceived: number;
|
|
814
|
-
packetsSent: number;
|
|
815
|
-
packetsReceived: number;
|
|
816
|
-
keepalivesSent: number;
|
|
817
|
-
keepalivesReceived: number;
|
|
818
|
-
uptimeSeconds: number;
|
|
819
|
-
quality?: IVpnConnectionQuality;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
interface IVpnServerStatistics extends IVpnStatistics {
|
|
823
|
-
activeClients: number;
|
|
824
|
-
totalConnections: number;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Connection quality (QoS)
|
|
828
|
-
type TVpnLinkHealth = 'healthy' | 'degraded' | 'critical';
|
|
829
|
-
|
|
830
|
-
interface IVpnConnectionQuality {
|
|
831
|
-
srttMs: number;
|
|
832
|
-
jitterMs: number;
|
|
833
|
-
minRttMs: number;
|
|
834
|
-
maxRttMs: number;
|
|
835
|
-
lossRatio: number;
|
|
836
|
-
consecutiveTimeouts: number;
|
|
837
|
-
linkHealth: TVpnLinkHealth;
|
|
838
|
-
currentKeepaliveIntervalSecs: number;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
// MTU info
|
|
842
|
-
interface IVpnMtuInfo {
|
|
843
|
-
tunMtu: number;
|
|
844
|
-
effectiveMtu: number;
|
|
845
|
-
linkMtu: number;
|
|
846
|
-
overheadBytes: number;
|
|
847
|
-
oversizedPacketsDropped: number;
|
|
848
|
-
icmpTooBigSent: number;
|
|
849
|
-
}
|
|
347
|
+
# Run Rust tests directly
|
|
348
|
+
cd rust && cargo test
|
|
850
349
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
assignedIp: string;
|
|
855
|
-
connectedSince: string;
|
|
856
|
-
bytesSent: number;
|
|
857
|
-
bytesReceived: number;
|
|
858
|
-
packetsDropped: number;
|
|
859
|
-
bytesDropped: number;
|
|
860
|
-
lastKeepaliveAt?: string;
|
|
861
|
-
keepalivesReceived: number;
|
|
862
|
-
rateLimitBytesPerSec?: number;
|
|
863
|
-
burstBytes?: number;
|
|
864
|
-
}
|
|
350
|
+
# Run a specific TS test
|
|
351
|
+
tstest test/test.flowcontrol.node.ts --verbose
|
|
352
|
+
```
|
|
865
353
|
|
|
866
|
-
|
|
867
|
-
interface IVpnClientTelemetry {
|
|
868
|
-
clientId: string;
|
|
869
|
-
assignedIp: string;
|
|
870
|
-
lastKeepaliveAt?: string;
|
|
871
|
-
keepalivesReceived: number;
|
|
872
|
-
packetsDropped: number;
|
|
873
|
-
bytesDropped: number;
|
|
874
|
-
bytesReceived: number;
|
|
875
|
-
bytesSent: number;
|
|
876
|
-
rateLimitBytesPerSec?: number;
|
|
877
|
-
burstBytes?: number;
|
|
878
|
-
}
|
|
354
|
+
### Project Structure
|
|
879
355
|
|
|
880
|
-
interface IVpnKeypair {
|
|
881
|
-
publicKey: string;
|
|
882
|
-
privateKey: string;
|
|
883
|
-
}
|
|
884
356
|
```
|
|
885
|
-
|
|
886
|
-
|
|
357
|
+
smartvpn/
|
|
358
|
+
├── ts/ # TypeScript control plane
|
|
359
|
+
│ ├── index.ts # All exports
|
|
360
|
+
│ ├── smartvpn.interfaces.ts # Interfaces, types, IPC command maps
|
|
361
|
+
│ ├── smartvpn.classes.vpnserver.ts
|
|
362
|
+
│ ├── smartvpn.classes.vpnclient.ts
|
|
363
|
+
│ ├── smartvpn.classes.vpnbridge.ts
|
|
364
|
+
│ ├── smartvpn.classes.vpnconfig.ts
|
|
365
|
+
│ ├── smartvpn.classes.vpninstaller.ts
|
|
366
|
+
│ └── smartvpn.classes.wgconfig.ts
|
|
367
|
+
├── rust/ # Rust data plane daemon
|
|
368
|
+
│ └── src/
|
|
369
|
+
│ ├── main.rs # CLI entry point
|
|
370
|
+
│ ├── server.rs # VPN server + hub methods
|
|
371
|
+
│ ├── client.rs # VPN client
|
|
372
|
+
│ ├── crypto.rs # Noise IK + XChaCha20
|
|
373
|
+
│ ├── client_registry.rs # Client database
|
|
374
|
+
│ ├── acl.rs # ACL engine
|
|
375
|
+
│ ├── proxy_protocol.rs # PROXY protocol v2 parser
|
|
376
|
+
│ ├── management.rs # JSON-lines IPC
|
|
377
|
+
│ ├── transport.rs # WebSocket transport
|
|
378
|
+
│ ├── quic_transport.rs # QUIC transport
|
|
379
|
+
│ ├── wireguard.rs # WireGuard (boringtun)
|
|
380
|
+
│ ├── codec.rs # Binary frame protocol
|
|
381
|
+
│ ├── keepalive.rs # Adaptive keepalives
|
|
382
|
+
│ ├── ratelimit.rs # Token bucket
|
|
383
|
+
│ └── ... # tunnel, network, telemetry, qos, mtu, reconnect
|
|
384
|
+
├── test/ # 9 test files (79 tests)
|
|
385
|
+
├── dist_ts/ # Compiled TypeScript
|
|
386
|
+
└── dist_rust/ # Cross-compiled binaries (linux amd64 + arm64)
|
|
387
|
+
```
|
|
887
388
|
|
|
888
389
|
## License and Legal Information
|
|
889
390
|
|
|
890
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [
|
|
391
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license.md) file.
|
|
891
392
|
|
|
892
393
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
893
394
|
|
|
@@ -899,7 +400,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
|
|
899
400
|
|
|
900
401
|
### Company Information
|
|
901
402
|
|
|
902
|
-
Task Venture Capital GmbH
|
|
403
|
+
Task Venture Capital GmbH
|
|
903
404
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
904
405
|
|
|
905
406
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|