feat(server): add bridge forwarding mode and per-client destination policy overrides

This commit is contained in:
2026-03-31 21:34:49 +00:00
parent 17af7ab289
commit fdeba5eeb5
12 changed files with 583 additions and 25 deletions

View File

@@ -9,7 +9,8 @@ A high-performance VPN solution with a **TypeScript control plane** and a **Rust
📊 **Per-transport metrics**: active clients and total connections broken down by websocket, QUIC, and WireGuard
🔄 **Hub API**: one `createClient()` call generates keys, assigns IP, returns both SmartVPN + WireGuard configs
📡 **Real-time telemetry**: RTT, jitter, loss ratio, link health — all via typed APIs
🌐 **Unified forwarding pipeline**: all transports share the same engine — TUN (kernel), userspace NAT (no root), or testing mode
🌐 **Unified forwarding pipeline**: all transports share the same engine — TUN (kernel), userspace NAT (no root), L2 bridge, or testing mode
🏠 **Bridge mode**: VPN clients get IPs from your LAN subnet — seamlessly bridge remote clients onto a physical network
🎯 **Destination routing policy**: force-target, block, or allow traffic per destination with nftables integration
**Handshake-driven WireGuard state**: peers appear as "connected" only after a successful WireGuard handshake, and auto-disconnect on idle timeout
@@ -84,7 +85,7 @@ await server.start({
publicKey: '<server-noise-public-key-base64>',
subnet: '10.8.0.0/24',
transportMode: 'all', // WebSocket + QUIC + WireGuard simultaneously (default)
forwardingMode: 'tun', // 'tun' (kernel), 'socket' (userspace NAT), or 'testing'
forwardingMode: 'tun', // 'tun' | 'socket' | 'bridge' | 'testing'
wgPrivateKey: '<server-wg-private-key-base64>', // required for WireGuard transport
enableNat: true,
dns: ['1.1.1.1', '8.8.8.8'],
@@ -237,6 +238,21 @@ In **TUN mode**, destination policies are enforced via **nftables** rules (using
In **socket mode**, the policy is evaluated in the userspace NAT engine before per-client ACLs.
**Per-client override** — individual clients can have their own destination policy that overrides the server-level default:
```typescript
await server.createClient({
clientId: 'restricted-client',
security: {
destinationPolicy: {
default: 'block', // block everything by default
allowList: ['10.0.0.0/8'], // except internal network
},
// ... other security settings
},
});
```
### 🔗 Socket Forward Proxy Protocol
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 +267,13 @@ await server.start({
### 📦 Packet Forwarding Modes
SmartVPN supports three forwarding modes, configurable per-server and per-client:
SmartVPN supports four forwarding modes, configurable per-server and per-client:
| Mode | Flag | Description | Root Required |
|------|------|-------------|---------------|
| **TUN** | `'tun'` | Kernel TUN device — real packet forwarding with system routing | ✅ Yes |
| **Userspace NAT** | `'socket'` | Userspace TCP/UDP proxy via `connect(2)` — no TUN, no root needed | ❌ No |
| **Bridge** | `'bridge'` | L2 bridge — VPN clients get IPs from a physical LAN subnet | ✅ Yes |
| **Testing** | `'testing'` | Monitoring only — packets are counted but not forwarded | ❌ No |
```typescript
@@ -267,6 +284,16 @@ await server.start({
enableNat: true,
});
// Server with bridge mode — VPN clients appear on the LAN
await server.start({
// ...
forwardingMode: 'bridge',
bridgeLanSubnet: '192.168.1.0/24', // LAN subnet to bridge into
bridgePhysicalInterface: 'eth0', // auto-detected if omitted
bridgeIpRangeStart: 200, // clients get .200.250 (defaults)
bridgeIpRangeEnd: 250,
});
// Client with TUN device
const { assignedIp } = await client.connect({
// ...
@@ -274,7 +301,9 @@ const { assignedIp } = await client.connect({
});
```
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.
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.
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.
### 📊 Telemetry & QoS
@@ -444,10 +473,10 @@ server.on('reconnected', () => { /* socket transport reconnected */ });
| Interface | Purpose |
|-----------|---------|
| `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode, clients, proxy protocol, destination policy) |
| `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode incl. bridge, clients, proxy protocol, destination policy) |
| `IVpnClientConfig` | Client configuration (server URL, keys, transport, forwarding mode, WG options, client-defined tags) |
| `IClientEntry` | Server-side client definition (ID, keys, security, priority, server/client tags, expiry) |
| `IClientSecurity` | Per-client ACLs and rate limits (SmartProxy-aligned naming) |
| `IClientSecurity` | Per-client ACLs, rate limits, and destination policy override (SmartProxy-aligned naming) |
| `IClientRateLimit` | Rate limiting config (bytesPerSec, burstBytes) |
| `IClientConfigBundle` | Full config bundle returned by `createClient()` — includes SmartVPN config, WireGuard .conf, and secrets |
| `IVpnClientInfo` | Connected client info (IP, stats, authenticated key, remote addr, transport type) |