@push.rocks/smartvpn 1.6.0 → 1.8.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 → 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 +143 -1
- package/package.json +6 -6
- package/readme.md +246 -773
- package/readme.plan.md +459 -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 +100 -1
package/readme.md
CHANGED
|
@@ -1,893 +1,366 @@
|
|
|
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
|
+
📊 **Adaptive QoS**: per-client rate limiting, priority queues, connection quality tracking
|
|
9
|
+
🔄 **Hub API**: one `createClient()` call generates keys, assigns IP, returns both SmartVPN + WireGuard configs
|
|
10
|
+
📡 **Real-time telemetry**: RTT, jitter, loss ratio, link health — all via typed APIs
|
|
11
11
|
|
|
12
12
|
## Issue Reporting and Security
|
|
13
13
|
|
|
14
14
|
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
15
|
|
|
16
|
-
## Install
|
|
16
|
+
## Install 📦
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
pnpm install @push.rocks/smartvpn
|
|
20
|
+
# or
|
|
21
|
+
npm install @push.rocks/smartvpn
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
The package ships with pre-compiled Rust binaries for **linux/amd64** and **linux/arm64**. No Rust toolchain required at runtime.
|
|
25
|
+
|
|
26
|
+
## Architecture 🏗️
|
|
23
27
|
|
|
24
28
|
```
|
|
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
|
-
└────────────────────────────────────┘
|
|
29
|
+
┌──────────────────────────────┐ JSON-lines IPC ┌───────────────────────────────┐
|
|
30
|
+
│ TypeScript Control Plane │ ◄─────────────────────► │ Rust Data Plane Daemon │
|
|
31
|
+
│ │ stdio or Unix sock │ │
|
|
32
|
+
│ VpnServer / VpnClient │ │ Noise IK handshake │
|
|
33
|
+
│ Typed IPC commands │ │ XChaCha20-Poly1305 │
|
|
34
|
+
│ Config validation │ │ WS + QUIC + WireGuard │
|
|
35
|
+
│ Hub: client management │ │ TUN device, IP pool, NAT │
|
|
36
|
+
│ WireGuard .conf generation │ │ Rate limiting, ACLs, QoS │
|
|
37
|
+
└──────────────────────────────┘ └───────────────────────────────┘
|
|
44
38
|
```
|
|
45
39
|
|
|
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 |
|
|
40
|
+
**Split-plane design** — TypeScript handles orchestration, config, and DX; Rust handles every hot-path byte with zero-copy async I/O (tokio, mimalloc).
|
|
60
41
|
|
|
61
|
-
##
|
|
42
|
+
## Quick Start 🚀
|
|
62
43
|
|
|
63
|
-
### VPN
|
|
44
|
+
### 1. Start a VPN Server (Hub)
|
|
64
45
|
|
|
65
46
|
```typescript
|
|
66
|
-
import {
|
|
67
|
-
|
|
68
|
-
const client = new VpnClient({
|
|
69
|
-
transport: { transport: 'stdio' },
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
await client.start();
|
|
47
|
+
import { VpnServer } from '@push.rocks/smartvpn';
|
|
73
48
|
|
|
74
|
-
const {
|
|
75
|
-
|
|
76
|
-
|
|
49
|
+
const server = new VpnServer({ transport: { transport: 'stdio' } });
|
|
50
|
+
await server.start({
|
|
51
|
+
listenAddr: '0.0.0.0:443',
|
|
52
|
+
privateKey: '<server-noise-private-key-base64>',
|
|
53
|
+
publicKey: '<server-noise-public-key-base64>',
|
|
54
|
+
subnet: '10.8.0.0/24',
|
|
55
|
+
transportMode: 'both', // WebSocket + QUIC simultaneously
|
|
56
|
+
enableNat: true,
|
|
77
57
|
dns: ['1.1.1.1', '8.8.8.8'],
|
|
78
|
-
mtu: 1420,
|
|
79
|
-
keepaliveIntervalSecs: 30,
|
|
80
58
|
});
|
|
81
|
-
|
|
82
|
-
console.log(`Connected! Assigned IP: ${assignedIp}`);
|
|
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, ... }
|
|
97
|
-
|
|
98
|
-
// Traffic stats (includes quality snapshot)
|
|
99
|
-
const stats = await client.getStatistics();
|
|
100
|
-
|
|
101
|
-
await client.disconnect();
|
|
102
|
-
client.stop();
|
|
103
59
|
```
|
|
104
60
|
|
|
105
|
-
###
|
|
61
|
+
### 2. Create a Client (One Call = Everything)
|
|
106
62
|
|
|
107
63
|
```typescript
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const { assignedIp } = await quicClient.connect({
|
|
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
|
-
});
|
|
123
|
-
|
|
124
|
-
// Or use auto-transport: tries QUIC first (3s timeout), falls back to WS
|
|
125
|
-
const autoClient = new VpnClient({
|
|
126
|
-
transport: { transport: 'stdio' },
|
|
64
|
+
const bundle = await server.createClient({
|
|
65
|
+
clientId: 'alice-laptop',
|
|
66
|
+
tags: ['engineering'],
|
|
67
|
+
security: {
|
|
68
|
+
destinationAllowList: ['10.0.0.0/8'], // can only reach internal network
|
|
69
|
+
destinationBlockList: ['10.0.0.99'], // except this host
|
|
70
|
+
rateLimit: { bytesPerSec: 10_000_000, burstBytes: 20_000_000 },
|
|
71
|
+
},
|
|
127
72
|
});
|
|
128
73
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
serverUrl: 'wss://vpn.example.com/tunnel', // WS URL — host:port extracted for QUIC attempt
|
|
133
|
-
serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY',
|
|
134
|
-
transport: 'auto', // default — QUIC first, then WS
|
|
135
|
-
});
|
|
74
|
+
// bundle.smartvpnConfig → typed IVpnClientConfig, ready to use
|
|
75
|
+
// bundle.wireguardConfig → standard WireGuard .conf string
|
|
76
|
+
// bundle.secrets → { noisePrivateKey, wgPrivateKey } — shown ONCE
|
|
136
77
|
```
|
|
137
78
|
|
|
138
|
-
###
|
|
79
|
+
### 3. Connect a Client
|
|
139
80
|
|
|
140
81
|
```typescript
|
|
141
82
|
import { VpnClient } from '@push.rocks/smartvpn';
|
|
142
83
|
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
await wgClient.start();
|
|
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
|
-
});
|
|
84
|
+
const client = new VpnClient({ transport: { transport: 'stdio' } });
|
|
85
|
+
await client.start();
|
|
163
86
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
wgClient.stop();
|
|
87
|
+
const { assignedIp } = await client.connect(bundle.smartvpnConfig);
|
|
88
|
+
console.log(`Connected! VPN IP: ${assignedIp}`);
|
|
167
89
|
```
|
|
168
90
|
|
|
169
|
-
|
|
91
|
+
## Features ✨
|
|
170
92
|
|
|
171
|
-
|
|
172
|
-
import { VpnServer } from '@push.rocks/smartvpn';
|
|
93
|
+
### 🔐 Enterprise Authentication (Noise IK)
|
|
173
94
|
|
|
174
|
-
|
|
175
|
-
transport: { transport: 'stdio' },
|
|
176
|
-
});
|
|
95
|
+
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.
|
|
177
96
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
97
|
+
- Per-client X25519 keypair generated server-side
|
|
98
|
+
- Client registry with enable/disable, expiry, tags
|
|
99
|
+
- Key rotation with `rotateClientKey()` — generates new keys, returns fresh config bundle, disconnects old session
|
|
181
100
|
|
|
182
|
-
|
|
183
|
-
await server.start({
|
|
184
|
-
listenAddr: '0.0.0.0:443',
|
|
185
|
-
privateKey: keypair.privateKey,
|
|
186
|
-
publicKey: keypair.publicKey,
|
|
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
|
|
200
|
-
});
|
|
101
|
+
### 🌐 Triple Transport
|
|
201
102
|
|
|
202
|
-
|
|
203
|
-
|
|
103
|
+
| Transport | Protocol | Best For |
|
|
104
|
+
|-----------|----------|----------|
|
|
105
|
+
| **WebSocket** | TLS over TCP | Firewall-friendly, Cloudflare compatible |
|
|
106
|
+
| **QUIC** | UDP (via quinn) | Low latency, datagram support for IP packets |
|
|
107
|
+
| **WireGuard** | UDP (via boringtun) | Standard WG clients (iOS, Android, wg-quick) |
|
|
204
108
|
|
|
205
|
-
|
|
206
|
-
await server.setClientRateLimit('client-id', 5_000_000, 10_000_000);
|
|
207
|
-
await server.removeClientRateLimit('client-id'); // unlimited
|
|
109
|
+
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).
|
|
208
110
|
|
|
209
|
-
|
|
210
|
-
const telemetry = await server.getClientTelemetry('client-id');
|
|
211
|
-
console.log(telemetry);
|
|
212
|
-
// {
|
|
213
|
-
// clientId, assignedIp, lastKeepaliveAt, keepalivesReceived,
|
|
214
|
-
// packetsDropped, bytesDropped, bytesReceived, bytesSent,
|
|
215
|
-
// rateLimitBytesPerSec, burstBytes
|
|
216
|
-
// }
|
|
111
|
+
### 🛡️ ACL Engine (SmartProxy-Aligned)
|
|
217
112
|
|
|
218
|
-
|
|
219
|
-
await server.disconnectClient('client-id');
|
|
113
|
+
Security policies per client, using the same `ipAllowList` / `ipBlockList` naming convention as `@push.rocks/smartproxy`:
|
|
220
114
|
|
|
221
|
-
|
|
222
|
-
|
|
115
|
+
```typescript
|
|
116
|
+
security: {
|
|
117
|
+
ipAllowList: ['192.168.1.0/24'], // source IPs allowed to connect
|
|
118
|
+
ipBlockList: ['192.168.1.100'], // deny overrides allow
|
|
119
|
+
destinationAllowList: ['10.0.0.0/8'], // VPN destinations permitted
|
|
120
|
+
destinationBlockList: ['10.0.0.99'], // deny overrides allow
|
|
121
|
+
maxConnections: 5,
|
|
122
|
+
rateLimit: { bytesPerSec: 1_000_000, burstBytes: 2_000_000 },
|
|
123
|
+
}
|
|
223
124
|
```
|
|
224
125
|
|
|
225
|
-
|
|
126
|
+
Supports exact IPs, CIDR, wildcards (`192.168.1.*`), and ranges (`1.1.1.1-1.1.1.100`).
|
|
226
127
|
|
|
227
|
-
|
|
228
|
-
import { VpnServer } from '@push.rocks/smartvpn';
|
|
128
|
+
### 📊 Telemetry & QoS
|
|
229
129
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
130
|
+
- **Connection quality**: Smoothed RTT, jitter, min/max RTT, loss ratio, link health (`healthy` / `degraded` / `critical`)
|
|
131
|
+
- **Adaptive keepalives**: Interval adjusts based on link health (60s → 30s → 10s)
|
|
132
|
+
- **Per-client rate limiting**: Token bucket with configurable bytes/sec and burst
|
|
133
|
+
- **Dead-peer detection**: 180s inactivity timeout
|
|
134
|
+
- **MTU management**: Automatic overhead calculation (IP+TCP+WS+Noise = 79 bytes)
|
|
233
135
|
|
|
234
|
-
|
|
235
|
-
await wgServer.start();
|
|
236
|
-
const keypair = await wgServer.generateWgKeypair();
|
|
237
|
-
console.log(`Server public key: ${keypair.publicKey}`);
|
|
136
|
+
### 🔄 Hub Client Management
|
|
238
137
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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,
|
|
257
|
-
});
|
|
138
|
+
The server acts as a **hub** — one API to manage all clients:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Create (generates keys, assigns IP, returns config bundle)
|
|
142
|
+
const bundle = await server.createClient({ clientId: 'bob-phone' });
|
|
143
|
+
|
|
144
|
+
// Read
|
|
145
|
+
const entry = await server.getClient('bob-phone');
|
|
146
|
+
const all = await server.listRegisteredClients();
|
|
258
147
|
|
|
259
|
-
//
|
|
260
|
-
await
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
persistentKeepalive: 25,
|
|
148
|
+
// Update (ACLs, tags, description, rate limits...)
|
|
149
|
+
await server.updateClient('bob-phone', {
|
|
150
|
+
security: { destinationAllowList: ['0.0.0.0/0'] },
|
|
151
|
+
tags: ['mobile', 'field-ops'],
|
|
264
152
|
});
|
|
265
153
|
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
154
|
+
// Enable / Disable
|
|
155
|
+
await server.disableClient('bob-phone'); // disconnects + blocks reconnection
|
|
156
|
+
await server.enableClient('bob-phone');
|
|
157
|
+
|
|
158
|
+
// Key rotation
|
|
159
|
+
const newBundle = await server.rotateClientKey('bob-phone');
|
|
271
160
|
|
|
272
|
-
//
|
|
273
|
-
await
|
|
161
|
+
// Export config (without secrets)
|
|
162
|
+
const wgConf = await server.exportClientConfig('bob-phone', 'wireguard');
|
|
274
163
|
|
|
275
|
-
|
|
276
|
-
|
|
164
|
+
// Remove
|
|
165
|
+
await server.removeClient('bob-phone');
|
|
277
166
|
```
|
|
278
167
|
|
|
279
|
-
###
|
|
168
|
+
### 📝 WireGuard Config Generation
|
|
280
169
|
|
|
281
|
-
|
|
170
|
+
Generate standard `.conf` files for any WireGuard client:
|
|
282
171
|
|
|
283
172
|
```typescript
|
|
284
173
|
import { WgConfigGenerator } from '@push.rocks/smartvpn';
|
|
285
174
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
privateKey: 'CLIENT_PRIVATE_KEY_BASE64',
|
|
175
|
+
const conf = WgConfigGenerator.generateClientConfig({
|
|
176
|
+
privateKey: '<client-wg-private-key>',
|
|
289
177
|
address: '10.8.0.2/24',
|
|
290
|
-
dns: ['1.1.1.1'
|
|
291
|
-
mtu: 1420,
|
|
178
|
+
dns: ['1.1.1.1'],
|
|
292
179
|
peer: {
|
|
293
|
-
publicKey: '
|
|
180
|
+
publicKey: '<server-wg-public-key>',
|
|
294
181
|
endpoint: 'vpn.example.com:51820',
|
|
295
|
-
allowedIps: ['0.0.0.0/0'
|
|
182
|
+
allowedIps: ['0.0.0.0/0'],
|
|
296
183
|
persistentKeepalive: 25,
|
|
297
|
-
presharedKey: 'OPTIONAL_PSK_BASE64',
|
|
298
184
|
},
|
|
299
185
|
});
|
|
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);
|
|
186
|
+
// → standard WireGuard .conf compatible with wg-quick, iOS, Android
|
|
322
187
|
```
|
|
323
188
|
|
|
324
|
-
|
|
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
|
-
},
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
await client.start(); // connects to existing daemon (does not spawn)
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
When using socket transport, `client.stop()` closes the socket but **does not kill the daemon** — exactly what you want in production.
|
|
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:
|
|
189
|
+
### 🖥️ System Service Installation
|
|
433
190
|
|
|
434
191
|
```typescript
|
|
435
192
|
import { VpnInstaller } from '@push.rocks/smartvpn';
|
|
436
193
|
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
// Linux (systemd)
|
|
440
|
-
const unit = VpnInstaller.generateSystemdUnit({
|
|
441
|
-
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
|
442
|
-
socketPath: '/var/run/smartvpn.sock',
|
|
194
|
+
const unit = VpnInstaller.generateServiceUnit({
|
|
443
195
|
mode: 'server',
|
|
196
|
+
configPath: '/etc/smartvpn/server.json',
|
|
444
197
|
});
|
|
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
|
-
});
|
|
198
|
+
// unit.platform → 'linux' | 'macos'
|
|
199
|
+
// unit.content → systemd unit file or launchd plist
|
|
200
|
+
// unit.installPath → /etc/systemd/system/smartvpn-server.service
|
|
520
201
|
```
|
|
521
202
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
203
|
+
## API Reference 📖
|
|
204
|
+
|
|
205
|
+
### Classes
|
|
206
|
+
|
|
207
|
+
| Class | Description |
|
|
208
|
+
|-------|-------------|
|
|
209
|
+
| `VpnServer` | Manages the Rust daemon in server mode. Hub methods for client CRUD. |
|
|
210
|
+
| `VpnClient` | Manages the Rust daemon in client mode. Connect, disconnect, telemetry. |
|
|
211
|
+
| `VpnBridge<T>` | Low-level typed IPC bridge (stdio or Unix socket). |
|
|
212
|
+
| `VpnConfig` | Static config validation and file I/O. |
|
|
213
|
+
| `VpnInstaller` | Generates systemd/launchd service files. |
|
|
214
|
+
| `WgConfigGenerator` | Generates standard WireGuard `.conf` files. |
|
|
215
|
+
|
|
216
|
+
### Key Interfaces
|
|
217
|
+
|
|
218
|
+
| Interface | Purpose |
|
|
219
|
+
|-----------|---------|
|
|
220
|
+
| `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, clients) |
|
|
221
|
+
| `IVpnClientConfig` | Client configuration (server URL, keys, transport, WG options) |
|
|
222
|
+
| `IClientEntry` | Server-side client definition (ID, keys, security, priority, tags, expiry) |
|
|
223
|
+
| `IClientSecurity` | Per-client ACLs and rate limits (SmartProxy-aligned naming) |
|
|
224
|
+
| `IClientRateLimit` | Rate limiting config (bytesPerSec, burstBytes) |
|
|
225
|
+
| `IClientConfigBundle` | Full config bundle returned by `createClient()` |
|
|
226
|
+
| `IVpnClientInfo` | Connected client info (IP, stats, authenticated key) |
|
|
227
|
+
| `IVpnConnectionQuality` | RTT, jitter, loss ratio, link health |
|
|
228
|
+
| `IVpnKeypair` | Base64-encoded public/private key pair |
|
|
229
|
+
|
|
230
|
+
### Server IPC Commands
|
|
231
|
+
|
|
232
|
+
| Command | Description |
|
|
233
|
+
|---------|-------------|
|
|
234
|
+
| `start` / `stop` | Start/stop the VPN listener |
|
|
235
|
+
| `createClient` | Generate keys, assign IP, return config bundle |
|
|
236
|
+
| `removeClient` / `getClient` / `listRegisteredClients` | Client registry CRUD |
|
|
237
|
+
| `updateClient` / `enableClient` / `disableClient` | Modify client state |
|
|
238
|
+
| `rotateClientKey` | Fresh keypairs + new config bundle |
|
|
239
|
+
| `exportClientConfig` | Re-export as SmartVPN config or WireGuard `.conf` |
|
|
240
|
+
| `listClients` / `disconnectClient` | Manage live connections |
|
|
241
|
+
| `setClientRateLimit` / `removeClientRateLimit` | Runtime rate limit adjustments |
|
|
242
|
+
| `getStatus` / `getStatistics` / `getClientTelemetry` | Monitoring |
|
|
243
|
+
| `generateKeypair` / `generateWgKeypair` / `generateClientKeypair` | Key generation |
|
|
244
|
+
| `addWgPeer` / `removeWgPeer` / `listWgPeers` | WireGuard peer management |
|
|
245
|
+
|
|
246
|
+
### Client IPC Commands
|
|
247
|
+
|
|
248
|
+
| Command | Description |
|
|
249
|
+
|---------|-------------|
|
|
250
|
+
| `connect` / `disconnect` | Manage the tunnel |
|
|
251
|
+
| `getStatus` / `getStatistics` | Connection state and traffic stats |
|
|
252
|
+
| `getConnectionQuality` | RTT, jitter, loss, link health |
|
|
253
|
+
| `getMtuInfo` | MTU and overhead details |
|
|
254
|
+
|
|
255
|
+
## Transport Modes 🔀
|
|
256
|
+
|
|
257
|
+
### Server Configuration
|
|
525
258
|
|
|
526
259
|
```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
|
-
```
|
|
260
|
+
// WebSocket only
|
|
261
|
+
{ transportMode: 'websocket', listenAddr: '0.0.0.0:443' }
|
|
545
262
|
|
|
546
|
-
|
|
263
|
+
// QUIC only
|
|
264
|
+
{ transportMode: 'quic', listenAddr: '0.0.0.0:443' }
|
|
547
265
|
|
|
548
|
-
|
|
266
|
+
// Both (WS + QUIC on same or different ports)
|
|
267
|
+
{ transportMode: 'both', listenAddr: '0.0.0.0:443', quicListenAddr: '0.0.0.0:4433' }
|
|
549
268
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
### Adaptive Keepalive
|
|
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');
|
|
269
|
+
// WireGuard
|
|
270
|
+
{ transportMode: 'wireguard', wgListenPort: 51820, wgPeers: [...] }
|
|
600
271
|
```
|
|
601
272
|
|
|
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:
|
|
273
|
+
### Client Configuration
|
|
646
274
|
|
|
647
275
|
```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
|
-
```
|
|
659
|
-
|
|
660
|
-
## 📦 Binary Protocol
|
|
276
|
+
// Auto (tries QUIC first, falls back to WS)
|
|
277
|
+
{ transport: 'auto', serverUrl: 'wss://vpn.example.com' }
|
|
661
278
|
|
|
662
|
-
|
|
279
|
+
// Explicit QUIC with certificate pinning
|
|
280
|
+
{ transport: 'quic', serverUrl: '1.2.3.4:4433', serverCertHash: '<sha256-base64>' }
|
|
663
281
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
│ Type (1B)│ Len (4B) │ Payload (variable) │
|
|
667
|
-
└──────────┴──────────┴────────────────────┘
|
|
282
|
+
// WireGuard
|
|
283
|
+
{ transport: 'wireguard', wgPrivateKey: '...', wgEndpoint: 'vpn.example.com:51820', ... }
|
|
668
284
|
```
|
|
669
285
|
|
|
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 |
|
|
286
|
+
## Cryptography 🔑
|
|
681
287
|
|
|
682
|
-
|
|
288
|
+
| Layer | Algorithm | Purpose |
|
|
289
|
+
|-------|-----------|---------|
|
|
290
|
+
| **Handshake** | Noise IK (X25519 + ChaChaPoly + BLAKE2s) | Mutual authentication + key exchange |
|
|
291
|
+
| **Transport** | Noise transport state (ChaChaPoly) | All post-handshake data encryption |
|
|
292
|
+
| **Additional** | XChaCha20-Poly1305 | Extended nonce space for data-at-rest |
|
|
293
|
+
| **WireGuard** | X25519 + ChaCha20-Poly1305 (via boringtun) | Standard WireGuard crypto |
|
|
683
294
|
|
|
684
|
-
|
|
295
|
+
## Binary Protocol 📡
|
|
685
296
|
|
|
686
|
-
|
|
297
|
+
All frames use `[type:1B][length:4B][payload:NB]` with a 64KB max payload:
|
|
687
298
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
# Generate a Noise keypair
|
|
697
|
-
smartvpn_daemon --generate-keypair
|
|
698
|
-
```
|
|
299
|
+
| Type | Hex | Direction | Description |
|
|
300
|
+
|------|-----|-----------|-------------|
|
|
301
|
+
| HandshakeInit | `0x01` | Client → Server | Noise IK first message |
|
|
302
|
+
| HandshakeResp | `0x02` | Server → Client | Noise IK response |
|
|
303
|
+
| IpPacket | `0x10` | Bidirectional | Encrypted tunnel data |
|
|
304
|
+
| Keepalive | `0x20` | Client → Server | App-level keepalive (not WS ping) |
|
|
305
|
+
| KeepaliveAck | `0x21` | Server → Client | Keepalive response with RTT payload |
|
|
306
|
+
| Disconnect | `0x3F` | Bidirectional | Graceful disconnect |
|
|
699
307
|
|
|
700
|
-
##
|
|
308
|
+
## Development 🛠️
|
|
701
309
|
|
|
702
310
|
```bash
|
|
703
311
|
# Install dependencies
|
|
704
312
|
pnpm install
|
|
705
313
|
|
|
706
|
-
# Build TypeScript + cross-compile
|
|
314
|
+
# Build (TypeScript + Rust cross-compile)
|
|
707
315
|
pnpm build
|
|
708
316
|
|
|
709
|
-
#
|
|
710
|
-
cd rust && cargo build
|
|
711
|
-
|
|
712
|
-
# Run all tests (82 Rust + 77 TypeScript)
|
|
713
|
-
cd rust && cargo test
|
|
317
|
+
# Run all tests (79 TS + 121 Rust = 200 tests)
|
|
714
318
|
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
319
|
|
|
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
|
-
}
|
|
320
|
+
# Run Rust tests directly
|
|
321
|
+
cd rust && cargo test
|
|
850
322
|
|
|
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
|
-
}
|
|
323
|
+
# Run a specific TS test
|
|
324
|
+
tstest test/test.flowcontrol.node.ts --verbose
|
|
325
|
+
```
|
|
865
326
|
|
|
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
|
-
}
|
|
327
|
+
### Project Structure
|
|
879
328
|
|
|
880
|
-
interface IVpnKeypair {
|
|
881
|
-
publicKey: string;
|
|
882
|
-
privateKey: string;
|
|
883
|
-
}
|
|
884
329
|
```
|
|
885
|
-
|
|
886
|
-
|
|
330
|
+
smartvpn/
|
|
331
|
+
├── ts/ # TypeScript control plane
|
|
332
|
+
│ ├── index.ts # All exports
|
|
333
|
+
│ ├── smartvpn.interfaces.ts # Interfaces, types, IPC command maps
|
|
334
|
+
│ ├── smartvpn.classes.vpnserver.ts
|
|
335
|
+
│ ├── smartvpn.classes.vpnclient.ts
|
|
336
|
+
│ ├── smartvpn.classes.vpnbridge.ts
|
|
337
|
+
│ ├── smartvpn.classes.vpnconfig.ts
|
|
338
|
+
│ ├── smartvpn.classes.vpninstaller.ts
|
|
339
|
+
│ └── smartvpn.classes.wgconfig.ts
|
|
340
|
+
├── rust/ # Rust data plane daemon
|
|
341
|
+
│ └── src/
|
|
342
|
+
│ ├── main.rs # CLI entry point
|
|
343
|
+
│ ├── server.rs # VPN server + hub methods
|
|
344
|
+
│ ├── client.rs # VPN client
|
|
345
|
+
│ ├── crypto.rs # Noise IK + XChaCha20
|
|
346
|
+
│ ├── client_registry.rs # Client database
|
|
347
|
+
│ ├── acl.rs # ACL engine
|
|
348
|
+
│ ├── management.rs # JSON-lines IPC
|
|
349
|
+
│ ├── transport.rs # WebSocket transport
|
|
350
|
+
│ ├── quic_transport.rs # QUIC transport
|
|
351
|
+
│ ├── wireguard.rs # WireGuard (boringtun)
|
|
352
|
+
│ ├── codec.rs # Binary frame protocol
|
|
353
|
+
│ ├── keepalive.rs # Adaptive keepalives
|
|
354
|
+
│ ├── ratelimit.rs # Token bucket
|
|
355
|
+
│ └── ... # tunnel, network, telemetry, qos, mtu, reconnect
|
|
356
|
+
├── test/ # 9 test files (79 tests)
|
|
357
|
+
├── dist_ts/ # Compiled TypeScript
|
|
358
|
+
└── dist_rust/ # Cross-compiled binaries (linux amd64 + arm64)
|
|
359
|
+
```
|
|
887
360
|
|
|
888
361
|
## License and Legal Information
|
|
889
362
|
|
|
890
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [
|
|
363
|
+
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
364
|
|
|
892
365
|
**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
366
|
|