feat(forwarding): add hybrid forwarding mode with per-client bridge and VLAN settings

This commit is contained in:
2026-04-01 03:47:26 +00:00
parent c49fcaf1ce
commit 180282ba86
8 changed files with 301 additions and 17 deletions

View File

@@ -2,17 +2,19 @@
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.
🔐 **Noise IK** mutual authentication — per-client X25519 keypairs, server-side registry
🚀 **Triple transport**: WebSocket (Cloudflare-friendly), raw **QUIC** (datagrams), and **WireGuard** (standard protocol)
🛡️ **ACL engine** — deny-overrides-allow IP filtering, aligned with SmartProxy conventions
🔀 **PROXY protocol v2** — real client IPs behind reverse proxies (HAProxy, SmartProxy, Cloudflare Spectrum)
📊 **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), 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
- 🔐 **Noise IK** mutual authentication — per-client X25519 keypairs, server-side registry
- 🚀 **Triple transport**: WebSocket (Cloudflare-friendly), raw **QUIC** (datagrams), and **WireGuard** (standard protocol)
- 🛡️ **ACL engine** — deny-overrides-allow IP filtering, aligned with SmartProxy conventions
- 🔀 **PROXY protocol v2** — real client IPs behind reverse proxies (HAProxy, SmartProxy, Cloudflare Spectrum)
- 📊 **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), L2 bridge, hybrid, or testing mode
- 🏠 **Bridge mode**: VPN clients get IPs from your LAN subnet — seamlessly bridge remote clients onto a physical network
- 🔀 **Hybrid mode**: per-client routing — some clients bridge to the LAN, others use userspace NAT, all on the same server
- 🏷️ **VLAN support**: assign individual clients to 802.1Q VLANs on the bridge
- 🎯 **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
## Issue Reporting and Security
@@ -85,7 +87,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' | 'socket' | 'bridge' | 'testing'
forwardingMode: 'tun', // 'tun' | 'socket' | 'bridge' | 'hybrid' | 'testing'
wgPrivateKey: '<server-wg-private-key-base64>', // required for WireGuard transport
enableNat: true,
dns: ['1.1.1.1', '8.8.8.8'],
@@ -267,13 +269,14 @@ await server.start({
### 📦 Packet Forwarding Modes
SmartVPN supports four forwarding modes, configurable per-server and per-client:
SmartVPN supports five forwarding modes, configurable per-server:
| 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 |
| **Hybrid** | `'hybrid'` | Per-client routing: some clients use socket NAT, others use bridge — both engines run simultaneously | ✅ Yes |
| **Testing** | `'testing'` | Monitoring only — packets are counted but not forwarded | ❌ No |
```typescript
@@ -294,6 +297,13 @@ await server.start({
bridgeIpRangeEnd: 250,
});
// Server with hybrid mode — per-client routing
await server.start({
// ...
forwardingMode: 'hybrid',
bridgePhysicalInterface: 'eth0', // for bridge clients
});
// Client with TUN device
const { assignedIp } = await client.connect({
// ...
@@ -305,6 +315,52 @@ The **userspace NAT** mode extracts destination IP/port from IP packets, opens a
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.
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.
### 🏠 Per-Client Bridge & VLAN Settings
When using `bridge` or `hybrid` mode, each client can be individually configured for LAN bridging, static IPs, DHCP, and 802.1Q VLAN assignment:
```typescript
// Client that bridges to the LAN with a static IP
await server.createClient({
clientId: 'office-printer',
useHostIp: true, // bridge to LAN instead of VPN subnet
staticIp: '192.168.1.210', // fixed LAN IP
});
// Client that gets a LAN IP via DHCP
await server.createClient({
clientId: 'roaming-laptop',
useHostIp: true,
useDhcp: true, // obtain IP from LAN DHCP server
});
// Client on a specific VLAN
await server.createClient({
clientId: 'iot-sensor',
useHostIp: true,
forceVlan: true,
vlanId: 100, // 802.1Q VLAN ID (1-4094)
});
// Regular NAT client (default, no bridge)
await server.createClient({
clientId: 'remote-worker',
// useHostIp defaults to false → uses socket NAT
});
```
| Field | Type | Description |
|-------|------|-------------|
| `useHostIp` | `boolean` | `true` = bridge to LAN (host IP), `false` = VPN subnet via NAT (default) |
| `useDhcp` | `boolean` | When `useHostIp` is true, obtain IP via DHCP relay instead of static/auto-assign |
| `staticIp` | `string` | Fixed LAN IP when `useHostIp` is true and `useDhcp` is false |
| `forceVlan` | `boolean` | Assign this client to a specific 802.1Q VLAN on the bridge |
| `vlanId` | `number` | VLAN ID (1-4094), required when `forceVlan` is true |
VLAN support uses Linux bridge VLAN filtering — each client's TAP port gets tagged with the specified VLAN ID, isolating traffic at Layer 2.
### 📊 Telemetry & QoS
- **Connection quality**: Smoothed RTT, jitter, min/max RTT, loss ratio, link health (`healthy` / `degraded` / `critical`)
@@ -473,9 +529,9 @@ server.on('reconnected', () => { /* socket transport reconnected */ });
| Interface | Purpose |
|-----------|---------|
| `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode incl. bridge, clients, proxy protocol, destination policy) |
| `IVpnServerConfig` | Server configuration (listen addr, keys, subnet, transport mode, forwarding mode incl. bridge/hybrid, 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) |
| `IClientEntry` | Server-side client definition (ID, keys, security, priority, server/client tags, expiry, bridge/VLAN settings) |
| `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 |