@push.rocks/smartvpn 1.14.0 → 1.16.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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartvpn',
6
- version: '1.14.0',
6
+ version: '1.16.0',
7
7
  description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHlFQUF5RTtDQUN2RixDQUFBIn0=
@@ -6,6 +6,10 @@ import type { IVpnServerOptions, IVpnServerConfig, IVpnStatus, IVpnServerStatist
6
6
  export declare class VpnServer extends plugins.events.EventEmitter {
7
7
  private bridge;
8
8
  private options;
9
+ private nft?;
10
+ private nftHealthInterval?;
11
+ private nftSubnet?;
12
+ private nftPolicy?;
9
13
  constructor(options: IVpnServerOptions);
10
14
  /**
11
15
  * Start the daemon bridge (spawn or connect).
@@ -104,6 +108,16 @@ export declare class VpnServer extends plugins.events.EventEmitter {
104
108
  * Generate a standalone Noise IK keypair (not tied to a client).
105
109
  */
106
110
  generateClientKeypair(): Promise<IVpnKeypair>;
111
+ /**
112
+ * Set up nftables rules for TUN mode destination policy.
113
+ * Also starts a 60-second health check interval to re-apply if rules are removed externally.
114
+ */
115
+ private setupTunDestinationPolicy;
116
+ /**
117
+ * Apply destination policy as nftables rules.
118
+ * Order: blockList (drop) → allowList (accept) → default action.
119
+ */
120
+ private applyDestinationPolicyRules;
107
121
  /**
108
122
  * Stop the daemon bridge.
109
123
  */
@@ -30,6 +30,10 @@ export class VpnServer extends plugins.events.EventEmitter {
30
30
  const cfg = config || this.options.config;
31
31
  if (cfg) {
32
32
  await this.bridge.sendCommand('start', { config: cfg });
33
+ // For TUN mode with a destination policy, set up nftables rules
34
+ if (cfg.forwardingMode === 'tun' && cfg.destinationPolicy) {
35
+ await this.setupTunDestinationPolicy(cfg.subnet, cfg.destinationPolicy);
36
+ }
33
37
  }
34
38
  }
35
39
  /**
@@ -180,10 +184,95 @@ export class VpnServer extends plugins.events.EventEmitter {
180
184
  async generateClientKeypair() {
181
185
  return this.bridge.sendCommand('generateClientKeypair', {});
182
186
  }
187
+ // ── TUN Destination Policy via nftables ──────────────────────────────
188
+ /**
189
+ * Set up nftables rules for TUN mode destination policy.
190
+ * Also starts a 60-second health check interval to re-apply if rules are removed externally.
191
+ */
192
+ async setupTunDestinationPolicy(subnet, policy) {
193
+ this.nftSubnet = subnet;
194
+ this.nftPolicy = policy;
195
+ this.nft = new plugins.smartnftables.SmartNftables({
196
+ tableName: 'smartvpn_tun',
197
+ dryRun: process.getuid?.() !== 0,
198
+ });
199
+ await this.nft.initialize();
200
+ await this.applyDestinationPolicyRules();
201
+ // Health check: re-apply rules if they disappear
202
+ this.nftHealthInterval = setInterval(async () => {
203
+ if (!this.nft)
204
+ return;
205
+ try {
206
+ const exists = await this.nft.tableExists();
207
+ if (!exists) {
208
+ console.warn('[smartvpn] nftables rules missing, re-applying destination policy');
209
+ this.nft = new plugins.smartnftables.SmartNftables({
210
+ tableName: 'smartvpn_tun',
211
+ });
212
+ await this.nft.initialize();
213
+ await this.applyDestinationPolicyRules();
214
+ }
215
+ }
216
+ catch (err) {
217
+ console.warn(`[smartvpn] nftables health check failed: ${err}`);
218
+ }
219
+ }, 60_000);
220
+ }
221
+ /**
222
+ * Apply destination policy as nftables rules.
223
+ * Order: blockList (drop) → allowList (accept) → default action.
224
+ */
225
+ async applyDestinationPolicyRules() {
226
+ if (!this.nft || !this.nftSubnet || !this.nftPolicy)
227
+ return;
228
+ const subnet = this.nftSubnet;
229
+ const policy = this.nftPolicy;
230
+ const family = 'ip';
231
+ const table = 'smartvpn_tun';
232
+ const commands = [];
233
+ // 1. Block list (deny wins — evaluated first)
234
+ if (policy.blockList) {
235
+ for (const dest of policy.blockList) {
236
+ commands.push(`nft add rule ${family} ${table} prerouting ip saddr ${subnet} ip daddr ${dest} drop`);
237
+ }
238
+ }
239
+ // 2. Allow list (pass through directly — skip DNAT)
240
+ if (policy.allowList) {
241
+ for (const dest of policy.allowList) {
242
+ commands.push(`nft add rule ${family} ${table} prerouting ip saddr ${subnet} ip daddr ${dest} accept`);
243
+ }
244
+ }
245
+ // 3. Default action
246
+ switch (policy.default) {
247
+ case 'forceTarget': {
248
+ const target = policy.target || '127.0.0.1';
249
+ commands.push(`nft add rule ${family} ${table} prerouting ip saddr ${subnet} dnat to ${target}`);
250
+ break;
251
+ }
252
+ case 'block':
253
+ commands.push(`nft add rule ${family} ${table} prerouting ip saddr ${subnet} drop`);
254
+ break;
255
+ case 'allow':
256
+ // No rule needed — kernel default allows
257
+ break;
258
+ }
259
+ if (commands.length > 0) {
260
+ await this.nft.applyRuleGroup('vpn-destination-policy', commands);
261
+ }
262
+ }
183
263
  /**
184
264
  * Stop the daemon bridge.
185
265
  */
186
266
  stop() {
267
+ // Clean up nftables rules
268
+ if (this.nftHealthInterval) {
269
+ clearInterval(this.nftHealthInterval);
270
+ this.nftHealthInterval = undefined;
271
+ }
272
+ if (this.nft) {
273
+ this.nft.cleanup().catch(() => { }); // best-effort cleanup
274
+ this.nft = undefined;
275
+ }
187
276
  this.bridge.stop();
188
277
  }
189
278
  /**
@@ -193,4 +282,4 @@ export class VpnServer extends plugins.events.EventEmitter {
193
282
  return this.bridge.running;
194
283
  }
195
284
  }
196
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR2cG4uY2xhc3Nlcy52cG5zZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHZwbi5jbGFzc2VzLnZwbnNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHVCQUF1QixDQUFDO0FBQ2pELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQWdCNUQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sU0FBVSxTQUFRLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWTtJQUl4RCxZQUFZLE9BQTBCO1FBQ3BDLEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLFNBQVMsQ0FBcUI7WUFDOUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxTQUFTO1lBQzVCLElBQUksRUFBRSxRQUFRO1NBQ2YsQ0FBQyxDQUFDO1FBRUgsd0JBQXdCO1FBQ3hCLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQW1CLEVBQUUsTUFBcUIsRUFBRSxFQUFFO1lBQ3BFLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDdEMsQ0FBQyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxhQUFhLEVBQUUsR0FBRyxFQUFFO1lBQ2pDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDM0IsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQXlCO1FBQzFDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMxQyxJQUFJLENBQUMsT0FBTyxF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
285
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR2cG4uY2xhc3Nlcy52cG5zZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHZwbi5jbGFzc2VzLnZwbnNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHVCQUF1QixDQUFDO0FBQ2pELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQWlCNUQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sU0FBVSxTQUFRLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWTtJQVF4RCxZQUFZLE9BQTBCO1FBQ3BDLEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLFNBQVMsQ0FBcUI7WUFDOUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxTQUFTO1lBQzVCLElBQUksRUFBRSxRQUFRO1NBQ2YsQ0FBQyxDQUFDO1FBRUgsd0JBQXdCO1FBQ3hCLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLElBQW1CLEVBQUUsTUFBcUIsRUFBRSxFQUFFO1lBQ3BFLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFDdEMsQ0FBQyxDQUFDLENBQUM7UUFDSCxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxhQUFhLEVBQUUsR0FBRyxFQUFFO1lBQ2pDLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7UUFDM0IsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQXlCO1FBQzFDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUMxQyxJQUFJLENBQUMsT0FBTyxF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
@@ -112,6 +112,14 @@ export interface IVpnServerConfig {
112
112
  * Controls where decrypted traffic goes: allow through, block, or redirect to a target.
113
113
  * Default: all traffic passes through (backward compatible). */
114
114
  destinationPolicy?: IDestinationPolicy;
115
+ /** Public endpoint address for generated client configs (e.g. 'vpn.example.com:51820').
116
+ * Used as the WireGuard `Endpoint =` and SmartVPN `serverUrl` host.
117
+ * Defaults to listenAddr (which is typically wrong for remote clients). */
118
+ serverEndpoint?: string;
119
+ /** AllowedIPs for generated WireGuard client configs.
120
+ * Controls what traffic the client routes through the VPN tunnel.
121
+ * Defaults to ['0.0.0.0/0'] (full tunnel). Set to e.g. ['10.8.0.0/24'] for split tunnel. */
122
+ clientAllowedIPs?: string[];
115
123
  }
116
124
  /**
117
125
  * Destination routing policy for VPN client traffic.
@@ -4,6 +4,7 @@ import * as os from 'os';
4
4
  import * as url from 'url';
5
5
  import * as events from 'events';
6
6
  export { path, fs, os, url, events };
7
+ import * as smartnftables from '@push.rocks/smartnftables';
7
8
  import * as smartpath from '@push.rocks/smartpath';
8
9
  import * as smartrust from '@push.rocks/smartrust';
9
- export { smartpath, smartrust };
10
+ export { smartnftables, smartpath, smartrust };
@@ -6,7 +6,8 @@ import * as url from 'url';
6
6
  import * as events from 'events';
7
7
  export { path, fs, os, url, events };
8
8
  // @push.rocks
9
+ import * as smartnftables from '@push.rocks/smartnftables';
9
10
  import * as smartpath from '@push.rocks/smartpath';
10
11
  import * as smartrust from '@push.rocks/smartrust';
11
- export { smartpath, smartrust };
12
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR2cG4ucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0dnBuLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBRWpDLE9BQU8sRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLENBQUM7QUFFckMsY0FBYztBQUNkLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFDbkQsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUVuRCxPQUFPLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxDQUFDIn0=
12
+ export { smartnftables, smartpath, smartrust };
13
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR2cG4ucGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3NtYXJ0dnBuLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYztBQUNkLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBRWpDLE9BQU8sRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLENBQUM7QUFFckMsY0FBYztBQUNkLE9BQU8sS0FBSyxhQUFhLE1BQU0sMkJBQTJCLENBQUM7QUFDM0QsT0FBTyxLQUFLLFNBQVMsTUFBTSx1QkFBdUIsQ0FBQztBQUNuRCxPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBRW5ELE9BQU8sRUFBRSxhQUFhLEVBQUUsU0FBUyxFQUFFLFNBQVMsRUFBRSxDQUFDIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartvpn",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "private": false,
5
5
  "description": "A VPN solution with TypeScript control plane and Rust data plane daemon",
6
6
  "type": "module",
@@ -29,6 +29,7 @@
29
29
  ],
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
+ "@push.rocks/smartnftables": "1.1.0",
32
33
  "@push.rocks/smartpath": "^6.0.0",
33
34
  "@push.rocks/smartrust": "^1.3.2"
34
35
  },
package/readme.md CHANGED
@@ -10,6 +10,7 @@ A high-performance VPN solution with a **TypeScript control plane** and a **Rust
10
10
  🔄 **Hub API**: one `createClient()` call generates keys, assigns IP, returns both SmartVPN + WireGuard configs
11
11
  📡 **Real-time telemetry**: RTT, jitter, loss ratio, link health — all via typed APIs
12
12
  🌐 **Unified forwarding pipeline**: all transports share the same engine — TUN (kernel), userspace NAT (no root), or testing mode
13
+ 🎯 **Destination routing policy**: force-target, block, or allow traffic per destination with nftables integration
13
14
 
14
15
  ## Issue Reporting and Security
15
16
 
@@ -36,11 +37,38 @@ The package ships with pre-compiled Rust binaries for **linux/amd64** and **linu
36
37
  │ Config validation │ │ WS + QUIC + WireGuard │
37
38
  │ Hub: client management │ │ TUN device, IP pool, NAT │
38
39
  │ WireGuard .conf generation │ │ Rate limiting, ACLs, QoS │
40
+ │ nftables destination policy │ │ Destination routing, nftables│
39
41
  └──────────────────────────────┘ └───────────────────────────────┘
40
42
  ```
41
43
 
42
44
  **Split-plane design** — TypeScript handles orchestration, config, and DX; Rust handles every hot-path byte with zero-copy async I/O (tokio, mimalloc).
43
45
 
46
+ ### IPC Transport Modes
47
+
48
+ The bridge between TypeScript and Rust supports two transport modes:
49
+
50
+ | Mode | Use Case | How It Works |
51
+ |------|----------|-------------|
52
+ | **stdio** | Development, testing | Spawns the Rust daemon as a child process, communicates over stdin/stdout |
53
+ | **socket** | Production | Connects to an already-running daemon via Unix domain socket, with optional auto-reconnect |
54
+
55
+ ```typescript
56
+ // Development: spawn the daemon
57
+ const server = new VpnServer({ transport: { transport: 'stdio' } });
58
+
59
+ // Production: connect to running daemon
60
+ const server = new VpnServer({
61
+ transport: {
62
+ transport: 'socket',
63
+ socketPath: '/var/run/smartvpn.sock',
64
+ autoReconnect: true,
65
+ reconnectBaseDelayMs: 100,
66
+ reconnectMaxDelayMs: 5000,
67
+ maxReconnectAttempts: 10,
68
+ },
69
+ });
70
+ ```
71
+
44
72
  ## Quick Start 🚀
45
73
 
46
74
  ### 1. Start a VPN Server (Hub)
@@ -54,8 +82,8 @@ await server.start({
54
82
  privateKey: '<server-noise-private-key-base64>',
55
83
  publicKey: '<server-noise-public-key-base64>',
56
84
  subnet: '10.8.0.0/24',
57
- transportMode: 'all', // WebSocket + QUIC + WireGuard simultaneously (default)
58
- forwardingMode: 'tun', // 'tun' (kernel), 'socket' (userspace NAT), or 'testing'
85
+ transportMode: 'all', // WebSocket + QUIC + WireGuard simultaneously (default)
86
+ forwardingMode: 'tun', // 'tun' (kernel), 'socket' (userspace NAT), or 'testing'
59
87
  wgPrivateKey: '<server-wg-private-key-base64>', // required for WireGuard transport
60
88
  enableNat: true,
61
89
  dns: ['1.1.1.1', '8.8.8.8'],
@@ -67,7 +95,7 @@ await server.start({
67
95
  ```typescript
68
96
  const bundle = await server.createClient({
69
97
  clientId: 'alice-laptop',
70
- tags: ['engineering'],
98
+ serverDefinedClientTags: ['engineering'], // trusted tags for access control
71
99
  security: {
72
100
  destinationAllowList: ['10.0.0.0/8'], // can only reach internal network
73
101
  destinationBlockList: ['10.0.0.99'], // except this host
@@ -155,6 +183,47 @@ await server.start({
155
183
  - `remoteAddr` field on `IVpnClientInfo` exposes the real client IP for monitoring
156
184
  - **Security**: must be `false` (default) when accepting direct connections — only enable behind a trusted proxy
157
185
 
186
+ ### 🎯 Destination Routing Policy
187
+
188
+ Control where decrypted VPN client traffic goes — force it to a specific target, block it, or allow it through. Evaluated per-packet before per-client ACLs.
189
+
190
+ ```typescript
191
+ await server.start({
192
+ // ...
193
+ forwardingMode: 'socket', // userspace NAT mode
194
+ destinationPolicy: {
195
+ default: 'forceTarget', // redirect all traffic to a target
196
+ target: '127.0.0.1', // target IP for 'forceTarget' mode
197
+ allowList: ['10.0.0.0/8'], // these destinations pass through directly
198
+ blockList: ['10.0.0.99'], // always blocked (deny overrides allow)
199
+ },
200
+ });
201
+ ```
202
+
203
+ **Policy modes:**
204
+
205
+ | Mode | Behavior |
206
+ |------|----------|
207
+ | `'forceTarget'` | Rewrites destination IP to `target` — funnels all traffic through a single endpoint |
208
+ | `'block'` | Drops all traffic not explicitly in `allowList` |
209
+ | `'allow'` | Passes all traffic through (default, backward compatible) |
210
+
211
+ In **TUN mode**, destination policies are enforced via **nftables** rules (using `@push.rocks/smartnftables`). A 60-second health check automatically re-applies rules if they're removed externally.
212
+
213
+ In **socket mode**, the policy is evaluated in the userspace NAT engine before per-client ACLs.
214
+
215
+ ### 🔗 Socket Forward Proxy Protocol
216
+
217
+ When using `forwardingMode: 'socket'` (userspace NAT), you can prepend **PROXY protocol v2 headers** on outbound TCP connections. This conveys the VPN client's tunnel IP as the source address to downstream services (e.g., SmartProxy):
218
+
219
+ ```typescript
220
+ await server.start({
221
+ // ...
222
+ forwardingMode: 'socket',
223
+ socketForwardProxyProtocol: true, // downstream sees VPN client IP, not 127.0.0.1
224
+ });
225
+ ```
226
+
158
227
  ### 📦 Packet Forwarding Modes
159
228
 
160
229
  SmartVPN supports three forwarding modes, configurable per-server and per-client:
@@ -190,6 +259,30 @@ The userspace NAT mode extracts destination IP/port from IP packets, opens a rea
190
259
  - **Dead-peer detection**: 180s inactivity timeout
191
260
  - **MTU management**: Automatic overhead calculation (IP+TCP+WS+Noise = 79 bytes)
192
261
 
262
+ ### 🏷️ Client Tags (Trusted vs Informational)
263
+
264
+ SmartVPN separates server-managed tags from client-reported tags:
265
+
266
+ | Field | Set By | Trust Level | Use For |
267
+ |-------|--------|-------------|---------|
268
+ | `serverDefinedClientTags` | Server admin (via `createClient` / `updateClient`) | ✅ Trusted | Access control, routing, billing |
269
+ | `clientDefinedClientTags` | Client (reported after connection) | ⚠️ Informational | Diagnostics, client self-identification |
270
+ | `tags` | *(deprecated)* | — | Legacy alias for `serverDefinedClientTags` |
271
+
272
+ ```typescript
273
+ // Server-side: trusted tags
274
+ await server.createClient({
275
+ clientId: 'alice-laptop',
276
+ serverDefinedClientTags: ['engineering', 'office-berlin'],
277
+ });
278
+
279
+ // Client-side: informational tags (reported to server)
280
+ await client.connect({
281
+ // ...
282
+ clientDefinedClientTags: ['macOS', 'v2.1.0'],
283
+ });
284
+ ```
285
+
193
286
  ### 🔄 Hub Client Management
194
287
 
195
288
  The server acts as a **hub** — one API to manage all clients:
@@ -205,7 +298,7 @@ const all = await server.listRegisteredClients();
205
298
  // Update (ACLs, tags, description, rate limits...)
206
299
  await server.updateClient('bob-phone', {
207
300
  security: { destinationAllowList: ['0.0.0.0/0'] },
208
- tags: ['mobile', 'field-ops'],
301
+ serverDefinedClientTags: ['mobile', 'field-ops'],
209
302
  });
210
303
 
211
304
  // Enable / Disable
@@ -243,46 +336,100 @@ const conf = WgConfigGenerator.generateClientConfig({
243
336
  // → standard WireGuard .conf compatible with wg-quick, iOS, Android
244
337
  ```
245
338
 
339
+ Server configs too:
340
+
341
+ ```typescript
342
+ const serverConf = WgConfigGenerator.generateServerConfig({
343
+ privateKey: '<server-wg-private-key>',
344
+ address: '10.8.0.1/24',
345
+ listenPort: 51820,
346
+ enableNat: true,
347
+ natInterface: 'eth0',
348
+ peers: [
349
+ { publicKey: '<client-wg-public-key>', allowedIps: ['10.8.0.2/32'] },
350
+ ],
351
+ });
352
+ ```
353
+
246
354
  ### 🖥️ System Service Installation
247
355
 
356
+ Generate systemd (Linux) or launchd (macOS) service units:
357
+
248
358
  ```typescript
249
359
  import { VpnInstaller } from '@push.rocks/smartvpn';
250
360
 
251
361
  const unit = VpnInstaller.generateServiceUnit({
362
+ binaryPath: '/usr/local/bin/smartvpn_daemon',
363
+ socketPath: '/var/run/smartvpn.sock',
252
364
  mode: 'server',
253
- configPath: '/etc/smartvpn/server.json',
254
365
  });
255
- // unit.platform → 'linux' | 'macos'
256
- // unit.content → systemd unit file or launchd plist
366
+ // unit.platform → 'linux' | 'macos'
367
+ // unit.content → systemd unit file or launchd plist
257
368
  // unit.installPath → /etc/systemd/system/smartvpn-server.service
258
369
  ```
259
370
 
371
+ You can also call `generateSystemdUnit()` or `generateLaunchdPlist()` directly for platform-specific options like custom descriptions.
372
+
373
+ ### 📢 Events
374
+
375
+ Both `VpnServer` and `VpnClient` extend `EventEmitter` and emit typed events:
376
+
377
+ ```typescript
378
+ server.on('client-connected', (info: IVpnClientInfo) => {
379
+ console.log(`${info.registeredClientId} connected from ${info.remoteAddr} via ${info.transportType}`);
380
+ });
381
+
382
+ server.on('client-disconnected', ({ clientId, reason }) => {
383
+ console.log(`${clientId} disconnected: ${reason}`);
384
+ });
385
+
386
+ client.on('status', (status: IVpnStatus) => {
387
+ console.log(`State: ${status.state}, IP: ${status.assignedIp}`);
388
+ });
389
+
390
+ // Both server and client emit:
391
+ server.on('exit', ({ code, signal }) => { /* daemon process exited */ });
392
+ server.on('reconnected', () => { /* socket transport reconnected */ });
393
+ ```
394
+
395
+ | Event | Emitted By | Payload |
396
+ |-------|-----------|---------|
397
+ | `status` | Both | `IVpnStatus` — connection state changes |
398
+ | `error` | Both | `{ message, code? }` |
399
+ | `client-connected` | Server | `IVpnClientInfo` — full client info including transport type |
400
+ | `client-disconnected` | Server | `{ clientId, reason? }` |
401
+ | `exit` | Both | `{ code, signal }` — daemon process exited |
402
+ | `reconnected` | Both | `void` — socket transport reconnected |
403
+
260
404
  ## API Reference 📖
261
405
 
262
406
  ### Classes
263
407
 
264
408
  | Class | Description |
265
409
  |-------|-------------|
266
- | `VpnServer` | Manages the Rust daemon in server mode. Hub methods for client CRUD. |
267
- | `VpnClient` | Manages the Rust daemon in client mode. Connect, disconnect, telemetry. |
268
- | `VpnBridge<T>` | Low-level typed IPC bridge (stdio or Unix socket). |
269
- | `VpnConfig` | Static config validation and file I/O. |
270
- | `VpnInstaller` | Generates systemd/launchd service files. |
271
- | `WgConfigGenerator` | Generates standard WireGuard `.conf` files. |
410
+ | `VpnServer` | Manages the Rust daemon in server mode. Hub methods for client CRUD, telemetry, rate limits, WireGuard peer management. |
411
+ | `VpnClient` | Manages the Rust daemon in client mode. Connect, disconnect, status, telemetry. |
412
+ | `VpnBridge<T>` | Low-level typed IPC bridge (stdio or Unix socket). Handles spawn, connect, reconnect, and typed command dispatch. |
413
+ | `VpnConfig` | Static config validation and JSON file I/O. Validates keys, addresses, CIDRs, MTU, etc. |
414
+ | `VpnInstaller` | Generates systemd/launchd service files for daemon deployment. |
415
+ | `WgConfigGenerator` | Generates standard WireGuard `.conf` files (client and server). |
272
416
 
273
417
  ### Key Interfaces
274
418
 
275
419
  | Interface | Purpose |
276
420
  |-----------|---------|
277
- | `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode, clients, proxy protocol) |
278
- | `IVpnClientConfig` | Client configuration (server URL, keys, transport, forwarding mode, WG options) |
279
- | `IClientEntry` | Server-side client definition (ID, keys, security, priority, tags, expiry) |
421
+ | `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode, clients, proxy protocol, destination policy) |
422
+ | `IVpnClientConfig` | Client configuration (server URL, keys, transport, forwarding mode, WG options, client-defined tags) |
423
+ | `IClientEntry` | Server-side client definition (ID, keys, security, priority, server/client tags, expiry) |
280
424
  | `IClientSecurity` | Per-client ACLs and rate limits (SmartProxy-aligned naming) |
281
425
  | `IClientRateLimit` | Rate limiting config (bytesPerSec, burstBytes) |
282
- | `IClientConfigBundle` | Full config bundle returned by `createClient()` |
283
- | `IVpnClientInfo` | Connected client info (IP, stats, authenticated key, remote addr) |
426
+ | `IClientConfigBundle` | Full config bundle returned by `createClient()` — includes SmartVPN config, WireGuard .conf, and secrets |
427
+ | `IVpnClientInfo` | Connected client info (IP, stats, authenticated key, remote addr, transport type) |
284
428
  | `IVpnConnectionQuality` | RTT, jitter, loss ratio, link health |
429
+ | `IVpnMtuInfo` | TUN MTU, effective MTU, overhead bytes, oversized packet stats |
285
430
  | `IVpnKeypair` | Base64-encoded public/private key pair |
431
+ | `IDestinationPolicy` | Destination routing policy (forceTarget / block / allow with allow/block lists) |
432
+ | `IVpnEventMap` | Typed event map for server and client EventEmitter |
286
433
 
287
434
  ### Server IPC Commands
288
435
 
@@ -317,7 +464,7 @@ const unit = VpnInstaller.generateServiceUnit({
317
464
  // All transports simultaneously (default) — WS + QUIC + WireGuard
318
465
  { transportMode: 'all', listenAddr: '0.0.0.0:443', wgPrivateKey: '...', wgListenPort: 51820 }
319
466
 
320
- // WS + QUIC only (backward compat)
467
+ // WS + QUIC only
321
468
  { transportMode: 'both', listenAddr: '0.0.0.0:443', quicListenAddr: '0.0.0.0:4433' }
322
469
 
323
470
  // WebSocket only
@@ -376,7 +523,7 @@ pnpm install
376
523
  # Build (TypeScript + Rust cross-compile)
377
524
  pnpm build
378
525
 
379
- # Run all tests (79 TS + 132 Rust = 211 tests)
526
+ # Run all tests
380
527
  pnpm test
381
528
 
382
529
  # Run Rust tests directly
@@ -393,6 +540,7 @@ smartvpn/
393
540
  ├── ts/ # TypeScript control plane
394
541
  │ ├── index.ts # All exports
395
542
  │ ├── smartvpn.interfaces.ts # Interfaces, types, IPC command maps
543
+ │ ├── smartvpn.plugins.ts # Dependency imports
396
544
  │ ├── smartvpn.classes.vpnserver.ts
397
545
  │ ├── smartvpn.classes.vpnclient.ts
398
546
  │ ├── smartvpn.classes.vpnbridge.ts
@@ -417,7 +565,7 @@ smartvpn/
417
565
  │ ├── ratelimit.rs # Token bucket
418
566
  │ ├── userspace_nat.rs # Userspace TCP/UDP NAT proxy
419
567
  │ └── ... # tunnel, network, telemetry, qos, mtu, reconnect
420
- ├── test/ # 9 test files (79 tests)
568
+ ├── test/ # Test files
421
569
  ├── dist_ts/ # Compiled TypeScript
422
570
  └── dist_rust/ # Cross-compiled binaries (linux amd64 + arm64)
423
571
  ```
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartvpn',
6
- version: '1.14.0',
6
+ version: '1.16.0',
7
7
  description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
8
8
  }
@@ -12,6 +12,7 @@ import type {
12
12
  IWgPeerInfo,
13
13
  IClientEntry,
14
14
  IClientConfigBundle,
15
+ IDestinationPolicy,
15
16
  TVpnServerCommands,
16
17
  } from './smartvpn.interfaces.js';
17
18
 
@@ -21,6 +22,10 @@ import type {
21
22
  export class VpnServer extends plugins.events.EventEmitter {
22
23
  private bridge: VpnBridge<TVpnServerCommands>;
23
24
  private options: IVpnServerOptions;
25
+ private nft?: plugins.smartnftables.SmartNftables;
26
+ private nftHealthInterval?: ReturnType<typeof setInterval>;
27
+ private nftSubnet?: string;
28
+ private nftPolicy?: IDestinationPolicy;
24
29
 
25
30
  constructor(options: IVpnServerOptions) {
26
31
  super();
@@ -50,6 +55,11 @@ export class VpnServer extends plugins.events.EventEmitter {
50
55
  const cfg = config || this.options.config;
51
56
  if (cfg) {
52
57
  await this.bridge.sendCommand('start', { config: cfg });
58
+
59
+ // For TUN mode with a destination policy, set up nftables rules
60
+ if (cfg.forwardingMode === 'tun' && cfg.destinationPolicy) {
61
+ await this.setupTunDestinationPolicy(cfg.subnet, cfg.destinationPolicy);
62
+ }
53
63
  }
54
64
  }
55
65
 
@@ -229,10 +239,110 @@ export class VpnServer extends plugins.events.EventEmitter {
229
239
  return this.bridge.sendCommand('generateClientKeypair', {} as Record<string, never>);
230
240
  }
231
241
 
242
+ // ── TUN Destination Policy via nftables ──────────────────────────────
243
+
244
+ /**
245
+ * Set up nftables rules for TUN mode destination policy.
246
+ * Also starts a 60-second health check interval to re-apply if rules are removed externally.
247
+ */
248
+ private async setupTunDestinationPolicy(subnet: string, policy: IDestinationPolicy): Promise<void> {
249
+ this.nftSubnet = subnet;
250
+ this.nftPolicy = policy;
251
+ this.nft = new plugins.smartnftables.SmartNftables({
252
+ tableName: 'smartvpn_tun',
253
+ dryRun: process.getuid?.() !== 0,
254
+ });
255
+
256
+ await this.nft.initialize();
257
+ await this.applyDestinationPolicyRules();
258
+
259
+ // Health check: re-apply rules if they disappear
260
+ this.nftHealthInterval = setInterval(async () => {
261
+ if (!this.nft) return;
262
+ try {
263
+ const exists = await this.nft.tableExists();
264
+ if (!exists) {
265
+ console.warn('[smartvpn] nftables rules missing, re-applying destination policy');
266
+ this.nft = new plugins.smartnftables.SmartNftables({
267
+ tableName: 'smartvpn_tun',
268
+ });
269
+ await this.nft.initialize();
270
+ await this.applyDestinationPolicyRules();
271
+ }
272
+ } catch (err) {
273
+ console.warn(`[smartvpn] nftables health check failed: ${err}`);
274
+ }
275
+ }, 60_000);
276
+ }
277
+
278
+ /**
279
+ * Apply destination policy as nftables rules.
280
+ * Order: blockList (drop) → allowList (accept) → default action.
281
+ */
282
+ private async applyDestinationPolicyRules(): Promise<void> {
283
+ if (!this.nft || !this.nftSubnet || !this.nftPolicy) return;
284
+
285
+ const subnet = this.nftSubnet;
286
+ const policy = this.nftPolicy;
287
+ const family = 'ip';
288
+ const table = 'smartvpn_tun';
289
+ const commands: string[] = [];
290
+
291
+ // 1. Block list (deny wins — evaluated first)
292
+ if (policy.blockList) {
293
+ for (const dest of policy.blockList) {
294
+ commands.push(
295
+ `nft add rule ${family} ${table} prerouting ip saddr ${subnet} ip daddr ${dest} drop`
296
+ );
297
+ }
298
+ }
299
+
300
+ // 2. Allow list (pass through directly — skip DNAT)
301
+ if (policy.allowList) {
302
+ for (const dest of policy.allowList) {
303
+ commands.push(
304
+ `nft add rule ${family} ${table} prerouting ip saddr ${subnet} ip daddr ${dest} accept`
305
+ );
306
+ }
307
+ }
308
+
309
+ // 3. Default action
310
+ switch (policy.default) {
311
+ case 'forceTarget': {
312
+ const target = policy.target || '127.0.0.1';
313
+ commands.push(
314
+ `nft add rule ${family} ${table} prerouting ip saddr ${subnet} dnat to ${target}`
315
+ );
316
+ break;
317
+ }
318
+ case 'block':
319
+ commands.push(
320
+ `nft add rule ${family} ${table} prerouting ip saddr ${subnet} drop`
321
+ );
322
+ break;
323
+ case 'allow':
324
+ // No rule needed — kernel default allows
325
+ break;
326
+ }
327
+
328
+ if (commands.length > 0) {
329
+ await this.nft.applyRuleGroup('vpn-destination-policy', commands);
330
+ }
331
+ }
332
+
232
333
  /**
233
334
  * Stop the daemon bridge.
234
335
  */
235
336
  public stop(): void {
337
+ // Clean up nftables rules
338
+ if (this.nftHealthInterval) {
339
+ clearInterval(this.nftHealthInterval);
340
+ this.nftHealthInterval = undefined;
341
+ }
342
+ if (this.nft) {
343
+ this.nft.cleanup().catch(() => {}); // best-effort cleanup
344
+ this.nft = undefined;
345
+ }
236
346
  this.bridge.stop();
237
347
  }
238
348
 
@@ -129,6 +129,14 @@ export interface IVpnServerConfig {
129
129
  * Controls where decrypted traffic goes: allow through, block, or redirect to a target.
130
130
  * Default: all traffic passes through (backward compatible). */
131
131
  destinationPolicy?: IDestinationPolicy;
132
+ /** Public endpoint address for generated client configs (e.g. 'vpn.example.com:51820').
133
+ * Used as the WireGuard `Endpoint =` and SmartVPN `serverUrl` host.
134
+ * Defaults to listenAddr (which is typically wrong for remote clients). */
135
+ serverEndpoint?: string;
136
+ /** AllowedIPs for generated WireGuard client configs.
137
+ * Controls what traffic the client routes through the VPN tunnel.
138
+ * Defaults to ['0.0.0.0/0'] (full tunnel). Set to e.g. ['10.8.0.0/24'] for split tunnel. */
139
+ clientAllowedIPs?: string[];
132
140
  }
133
141
 
134
142
  /**
@@ -8,7 +8,8 @@ import * as events from 'events';
8
8
  export { path, fs, os, url, events };
9
9
 
10
10
  // @push.rocks
11
+ import * as smartnftables from '@push.rocks/smartnftables';
11
12
  import * as smartpath from '@push.rocks/smartpath';
12
13
  import * as smartrust from '@push.rocks/smartrust';
13
14
 
14
- export { smartpath, smartrust };
15
+ export { smartnftables, smartpath, smartrust };