@push.rocks/smartvpn 1.17.1 → 1.19.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.17.1',
6
+ version: '1.19.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=
@@ -75,8 +75,9 @@ export interface IVpnServerConfig {
75
75
  /** Enable NAT/masquerade for client traffic */
76
76
  enableNat?: boolean;
77
77
  /** Forwarding mode: 'tun' (kernel TUN, requires root), 'socket' (userspace NAT),
78
+ * 'bridge' (L2 bridge to host LAN), 'hybrid' (per-client socket+bridge),
78
79
  * or 'testing' (monitoring only). Default: 'testing'. */
79
- forwardingMode?: 'tun' | 'socket' | 'testing';
80
+ forwardingMode?: 'tun' | 'socket' | 'bridge' | 'hybrid' | 'testing';
80
81
  /** Default rate limit for new clients (bytes/sec). Omit for unlimited. */
81
82
  defaultRateLimitBytesPerSec?: number;
82
83
  /** Default burst size for new clients (bytes). Omit for unlimited. */
@@ -120,6 +121,19 @@ export interface IVpnServerConfig {
120
121
  * Controls what traffic the client routes through the VPN tunnel.
121
122
  * Defaults to ['0.0.0.0/0'] (full tunnel). Set to e.g. ['10.8.0.0/24'] for split tunnel. */
122
123
  clientAllowedIPs?: string[];
124
+ /** LAN subnet CIDR for bridge mode (e.g. '192.168.1.0/24').
125
+ * VPN clients get IPs from this subnet instead of the VPN subnet.
126
+ * Required when forwardingMode is 'bridge'. */
127
+ bridgeLanSubnet?: string;
128
+ /** Physical network interface to bridge (e.g. 'eth0').
129
+ * Auto-detected from the default route if omitted. */
130
+ bridgePhysicalInterface?: string;
131
+ /** Start of VPN client IP range within the LAN subnet (host offset, e.g. 200 for .200).
132
+ * Default: 200. */
133
+ bridgeIpRangeStart?: number;
134
+ /** End of VPN client IP range within the LAN subnet (host offset, e.g. 250 for .250).
135
+ * Default: 250. */
136
+ bridgeIpRangeEnd?: number;
123
137
  }
124
138
  /**
125
139
  * Destination routing policy for VPN client traffic.
@@ -253,6 +267,10 @@ export interface IClientSecurity {
253
267
  maxConnections?: number;
254
268
  /** Per-client rate limiting. */
255
269
  rateLimit?: IClientRateLimit;
270
+ /** Per-client destination routing policy override.
271
+ * When set, overrides the server-level destinationPolicy for this client's traffic.
272
+ * Supports the same options: forceTarget, block, allow with allow/block lists. */
273
+ destinationPolicy?: IDestinationPolicy;
256
274
  }
257
275
  /**
258
276
  * Server-side client definition — the central config object for the Hub.
@@ -283,6 +301,18 @@ export interface IClientEntry {
283
301
  expiresAt?: string;
284
302
  /** Assigned VPN IP address (set by server) */
285
303
  assignedIp?: string;
304
+ /** If true, client gets a host network IP via bridge mode (L2 to LAN).
305
+ * If false (default), client gets a VPN subnet IP via socket/NAT mode. */
306
+ useHostIp?: boolean;
307
+ /** If true and useHostIp is true, obtain IP via DHCP relay.
308
+ * If false or omitted, use staticIp or auto-assign from bridge IP range. */
309
+ useDhcp?: boolean;
310
+ /** Static LAN IP when useHostIp is true and useDhcp is false. */
311
+ staticIp?: string;
312
+ /** If true, assign this client to a specific 802.1Q VLAN on the bridge. */
313
+ forceVlan?: boolean;
314
+ /** 802.1Q VLAN ID (1-4094). Required when forceVlan is true. */
315
+ vlanId?: number;
286
316
  }
287
317
  /**
288
318
  * Complete client config bundle — returned by createClient() and rotateClientKey().
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartvpn",
3
- "version": "1.17.1",
3
+ "version": "1.19.0",
4
4
  "private": false,
5
5
  "description": "A VPN solution with TypeScript control plane and Rust data plane daemon",
6
6
  "type": "module",
package/readme.md CHANGED
@@ -2,16 +2,19 @@
2
2
 
3
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
- 🔐 **Noise IK** mutual authentication — per-client X25519 keypairs, server-side registry
6
- 🚀 **Triple transport**: WebSocket (Cloudflare-friendly), raw **QUIC** (datagrams), and **WireGuard** (standard protocol)
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
- 📊 **Per-transport metrics**: active clients and total connections broken down by websocket, QUIC, and WireGuard
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
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
14
- **Handshake-driven WireGuard state**: peers appear as "connected" only after a successful WireGuard handshake, and auto-disconnect on idle timeout
5
+ - 🔐 **Noise IK** mutual authentication — per-client X25519 keypairs, server-side registry
6
+ - 🚀 **Triple transport**: WebSocket (Cloudflare-friendly), raw **QUIC** (datagrams), and **WireGuard** (standard protocol)
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
+ - 📊 **Per-transport metrics**: active clients and total connections broken down by websocket, QUIC, and WireGuard
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
12
+ - 🌐 **Unified forwarding pipeline**: all transports share the same engine — TUN (kernel), userspace NAT (no root), L2 bridge, hybrid, or testing mode
13
+ - 🏠 **Bridge mode**: VPN clients get IPs from your LAN subnet seamlessly bridge remote clients onto a physical network
14
+ - 🔀 **Hybrid mode**: per-client routing some clients bridge to the LAN, others use userspace NAT, all on the same server
15
+ - 🏷️ **VLAN support**: assign individual clients to 802.1Q VLANs on the bridge
16
+ - 🎯 **Destination routing policy**: force-target, block, or allow traffic per destination with nftables integration
17
+ - ⚡ **Handshake-driven WireGuard state**: peers appear as "connected" only after a successful WireGuard handshake, and auto-disconnect on idle timeout
15
18
 
16
19
  ## Issue Reporting and Security
17
20
 
@@ -84,7 +87,7 @@ await server.start({
84
87
  publicKey: '<server-noise-public-key-base64>',
85
88
  subnet: '10.8.0.0/24',
86
89
  transportMode: 'all', // WebSocket + QUIC + WireGuard simultaneously (default)
87
- forwardingMode: 'tun', // 'tun' (kernel), 'socket' (userspace NAT), or 'testing'
90
+ forwardingMode: 'tun', // 'tun' | 'socket' | 'bridge' | 'hybrid' | 'testing'
88
91
  wgPrivateKey: '<server-wg-private-key-base64>', // required for WireGuard transport
89
92
  enableNat: true,
90
93
  dns: ['1.1.1.1', '8.8.8.8'],
@@ -237,6 +240,21 @@ In **TUN mode**, destination policies are enforced via **nftables** rules (using
237
240
 
238
241
  In **socket mode**, the policy is evaluated in the userspace NAT engine before per-client ACLs.
239
242
 
243
+ **Per-client override** — individual clients can have their own destination policy that overrides the server-level default:
244
+
245
+ ```typescript
246
+ await server.createClient({
247
+ clientId: 'restricted-client',
248
+ security: {
249
+ destinationPolicy: {
250
+ default: 'block', // block everything by default
251
+ allowList: ['10.0.0.0/8'], // except internal network
252
+ },
253
+ // ... other security settings
254
+ },
255
+ });
256
+ ```
257
+
240
258
  ### 🔗 Socket Forward Proxy Protocol
241
259
 
242
260
  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):
@@ -251,12 +269,14 @@ await server.start({
251
269
 
252
270
  ### 📦 Packet Forwarding Modes
253
271
 
254
- SmartVPN supports three forwarding modes, configurable per-server and per-client:
272
+ SmartVPN supports five forwarding modes, configurable per-server:
255
273
 
256
274
  | Mode | Flag | Description | Root Required |
257
275
  |------|------|-------------|---------------|
258
276
  | **TUN** | `'tun'` | Kernel TUN device — real packet forwarding with system routing | ✅ Yes |
259
277
  | **Userspace NAT** | `'socket'` | Userspace TCP/UDP proxy via `connect(2)` — no TUN, no root needed | ❌ No |
278
+ | **Bridge** | `'bridge'` | L2 bridge — VPN clients get IPs from a physical LAN subnet | ✅ Yes |
279
+ | **Hybrid** | `'hybrid'` | Per-client routing: some clients use socket NAT, others use bridge — both engines run simultaneously | ✅ Yes |
260
280
  | **Testing** | `'testing'` | Monitoring only — packets are counted but not forwarded | ❌ No |
261
281
 
262
282
  ```typescript
@@ -267,6 +287,23 @@ await server.start({
267
287
  enableNat: true,
268
288
  });
269
289
 
290
+ // Server with bridge mode — VPN clients appear on the LAN
291
+ await server.start({
292
+ // ...
293
+ forwardingMode: 'bridge',
294
+ bridgeLanSubnet: '192.168.1.0/24', // LAN subnet to bridge into
295
+ bridgePhysicalInterface: 'eth0', // auto-detected if omitted
296
+ bridgeIpRangeStart: 200, // clients get .200–.250 (defaults)
297
+ bridgeIpRangeEnd: 250,
298
+ });
299
+
300
+ // Server with hybrid mode — per-client routing
301
+ await server.start({
302
+ // ...
303
+ forwardingMode: 'hybrid',
304
+ bridgePhysicalInterface: 'eth0', // for bridge clients
305
+ });
306
+
270
307
  // Client with TUN device
271
308
  const { assignedIp } = await client.connect({
272
309
  // ...
@@ -274,7 +311,55 @@ const { assignedIp } = await client.connect({
274
311
  });
275
312
  ```
276
313
 
277
- The userspace NAT mode extracts destination IP/port from IP packets, opens a real socket to the destination, and relays data — supporting both TCP streams and UDP datagrams without requiring `CAP_NET_ADMIN` or root privileges.
314
+ The **userspace NAT** mode extracts destination IP/port from IP packets, opens a real socket to the destination, and relays data — supporting both TCP streams and UDP datagrams without requiring `CAP_NET_ADMIN` or root privileges.
315
+
316
+ The **bridge** mode assigns VPN clients IPs from a real LAN subnet instead of a virtual VPN subnet. Clients appear as if they're directly on the physical network — perfect for remote access to home labs, office networks, or IoT devices.
317
+
318
+ The **hybrid** mode runs both engines simultaneously with a **per-client routing table**. Each client's `useHostIp` flag determines whether its packets go through the bridge (L2, LAN IP) or socket NAT (userspace, VPN IP). This is ideal when most clients need internet NAT but some need direct LAN access.
319
+
320
+ ### 🏠 Per-Client Bridge & VLAN Settings
321
+
322
+ When using `bridge` or `hybrid` mode, each client can be individually configured for LAN bridging, static IPs, DHCP, and 802.1Q VLAN assignment:
323
+
324
+ ```typescript
325
+ // Client that bridges to the LAN with a static IP
326
+ await server.createClient({
327
+ clientId: 'office-printer',
328
+ useHostIp: true, // bridge to LAN instead of VPN subnet
329
+ staticIp: '192.168.1.210', // fixed LAN IP
330
+ });
331
+
332
+ // Client that gets a LAN IP via DHCP
333
+ await server.createClient({
334
+ clientId: 'roaming-laptop',
335
+ useHostIp: true,
336
+ useDhcp: true, // obtain IP from LAN DHCP server
337
+ });
338
+
339
+ // Client on a specific VLAN
340
+ await server.createClient({
341
+ clientId: 'iot-sensor',
342
+ useHostIp: true,
343
+ forceVlan: true,
344
+ vlanId: 100, // 802.1Q VLAN ID (1-4094)
345
+ });
346
+
347
+ // Regular NAT client (default, no bridge)
348
+ await server.createClient({
349
+ clientId: 'remote-worker',
350
+ // useHostIp defaults to false → uses socket NAT
351
+ });
352
+ ```
353
+
354
+ | Field | Type | Description |
355
+ |-------|------|-------------|
356
+ | `useHostIp` | `boolean` | `true` = bridge to LAN (host IP), `false` = VPN subnet via NAT (default) |
357
+ | `useDhcp` | `boolean` | When `useHostIp` is true, obtain IP via DHCP relay instead of static/auto-assign |
358
+ | `staticIp` | `string` | Fixed LAN IP when `useHostIp` is true and `useDhcp` is false |
359
+ | `forceVlan` | `boolean` | Assign this client to a specific 802.1Q VLAN on the bridge |
360
+ | `vlanId` | `number` | VLAN ID (1-4094), required when `forceVlan` is true |
361
+
362
+ VLAN support uses Linux bridge VLAN filtering — each client's TAP port gets tagged with the specified VLAN ID, isolating traffic at Layer 2.
278
363
 
279
364
  ### 📊 Telemetry & QoS
280
365
 
@@ -444,10 +529,10 @@ server.on('reconnected', () => { /* socket transport reconnected */ });
444
529
 
445
530
  | Interface | Purpose |
446
531
  |-----------|---------|
447
- | `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode, clients, proxy protocol, destination policy) |
532
+ | `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode incl. bridge/hybrid, clients, proxy protocol, destination policy) |
448
533
  | `IVpnClientConfig` | Client configuration (server URL, keys, transport, forwarding mode, WG options, client-defined tags) |
449
- | `IClientEntry` | Server-side client definition (ID, keys, security, priority, server/client tags, expiry) |
450
- | `IClientSecurity` | Per-client ACLs and rate limits (SmartProxy-aligned naming) |
534
+ | `IClientEntry` | Server-side client definition (ID, keys, security, priority, server/client tags, expiry, bridge/VLAN settings) |
535
+ | `IClientSecurity` | Per-client ACLs, rate limits, and destination policy override (SmartProxy-aligned naming) |
451
536
  | `IClientRateLimit` | Rate limiting config (bytesPerSec, burstBytes) |
452
537
  | `IClientConfigBundle` | Full config bundle returned by `createClient()` — includes SmartVPN config, WireGuard .conf, and secrets |
453
538
  | `IVpnClientInfo` | Connected client info (IP, stats, authenticated key, remote addr, transport type) |
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartvpn',
6
- version: '1.17.1',
6
+ version: '1.19.0',
7
7
  description: 'A VPN solution with TypeScript control plane and Rust data plane daemon'
8
8
  }
@@ -92,8 +92,9 @@ export interface IVpnServerConfig {
92
92
  /** Enable NAT/masquerade for client traffic */
93
93
  enableNat?: boolean;
94
94
  /** Forwarding mode: 'tun' (kernel TUN, requires root), 'socket' (userspace NAT),
95
+ * 'bridge' (L2 bridge to host LAN), 'hybrid' (per-client socket+bridge),
95
96
  * or 'testing' (monitoring only). Default: 'testing'. */
96
- forwardingMode?: 'tun' | 'socket' | 'testing';
97
+ forwardingMode?: 'tun' | 'socket' | 'bridge' | 'hybrid' | 'testing';
97
98
  /** Default rate limit for new clients (bytes/sec). Omit for unlimited. */
98
99
  defaultRateLimitBytesPerSec?: number;
99
100
  /** Default burst size for new clients (bytes). Omit for unlimited. */
@@ -137,6 +138,22 @@ export interface IVpnServerConfig {
137
138
  * Controls what traffic the client routes through the VPN tunnel.
138
139
  * Defaults to ['0.0.0.0/0'] (full tunnel). Set to e.g. ['10.8.0.0/24'] for split tunnel. */
139
140
  clientAllowedIPs?: string[];
141
+
142
+ // Bridge mode configuration (forwardingMode: 'bridge')
143
+
144
+ /** LAN subnet CIDR for bridge mode (e.g. '192.168.1.0/24').
145
+ * VPN clients get IPs from this subnet instead of the VPN subnet.
146
+ * Required when forwardingMode is 'bridge'. */
147
+ bridgeLanSubnet?: string;
148
+ /** Physical network interface to bridge (e.g. 'eth0').
149
+ * Auto-detected from the default route if omitted. */
150
+ bridgePhysicalInterface?: string;
151
+ /** Start of VPN client IP range within the LAN subnet (host offset, e.g. 200 for .200).
152
+ * Default: 200. */
153
+ bridgeIpRangeStart?: number;
154
+ /** End of VPN client IP range within the LAN subnet (host offset, e.g. 250 for .250).
155
+ * Default: 250. */
156
+ bridgeIpRangeEnd?: number;
140
157
  }
141
158
 
142
159
  /**
@@ -310,6 +327,10 @@ export interface IClientSecurity {
310
327
  maxConnections?: number;
311
328
  /** Per-client rate limiting. */
312
329
  rateLimit?: IClientRateLimit;
330
+ /** Per-client destination routing policy override.
331
+ * When set, overrides the server-level destinationPolicy for this client's traffic.
332
+ * Supports the same options: forceTarget, block, allow with allow/block lists. */
333
+ destinationPolicy?: IDestinationPolicy;
313
334
  }
314
335
 
315
336
  /**
@@ -341,6 +362,21 @@ export interface IClientEntry {
341
362
  expiresAt?: string;
342
363
  /** Assigned VPN IP address (set by server) */
343
364
  assignedIp?: string;
365
+
366
+ // Per-client bridge/host-IP settings
367
+
368
+ /** If true, client gets a host network IP via bridge mode (L2 to LAN).
369
+ * If false (default), client gets a VPN subnet IP via socket/NAT mode. */
370
+ useHostIp?: boolean;
371
+ /** If true and useHostIp is true, obtain IP via DHCP relay.
372
+ * If false or omitted, use staticIp or auto-assign from bridge IP range. */
373
+ useDhcp?: boolean;
374
+ /** Static LAN IP when useHostIp is true and useDhcp is false. */
375
+ staticIp?: string;
376
+ /** If true, assign this client to a specific 802.1Q VLAN on the bridge. */
377
+ forceVlan?: boolean;
378
+ /** 802.1Q VLAN ID (1-4094). Required when forceVlan is true. */
379
+ vlanId?: number;
344
380
  }
345
381
 
346
382
  /**