From cc3a7cb5b68d1a320adff1f14eaeb7d944a61c95 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 30 Mar 2026 13:06:14 +0000 Subject: [PATCH] feat(vpn): add destination-based VPN routing policy and standardize socket proxy forwarding --- changelog.md | 8 +++++++ package.json | 2 +- pnpm-lock.yaml | 10 ++++---- ts/00_commitinfo_data.ts | 2 +- ts/classes.dcrouter.ts | 35 ++++++++++++++-------------- ts/opsserver/handlers/vpn.handler.ts | 2 -- ts/vpn/classes.vpn-manager.ts | 26 ++++++++++----------- ts_interfaces/data/vpn.ts | 1 - ts_web/00_commitinfo_data.ts | 2 +- ts_web/elements/ops-view-vpn.ts | 6 +---- 10 files changed, 47 insertions(+), 47 deletions(-) diff --git a/changelog.md b/changelog.md index 9ea84b1..d4fcd45 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2026-03-30 - 11.16.0 - feat(vpn) +add destination-based VPN routing policy and standardize socket proxy forwarding + +- replace configurable VPN forwarding mode with socket-based forwarding and always enable proxy protocol support to SmartProxy from localhost +- add destinationPolicy configuration for controlling default VPN traffic handling, including forceTarget, allow, and block rules +- remove forwarding mode reporting from VPN status APIs, logs, and ops UI to reflect the simplified VPN runtime model +- update @push.rocks/smartvpn to 1.14.0 to support the new VPN routing behavior + ## 2026-03-30 - 11.15.0 - feat(vpn) add tag-based VPN route access control and support configured initial VPN clients diff --git a/package.json b/package.json index e00157c..9ca1508 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@push.rocks/smartrx": "^3.0.10", "@push.rocks/smartstate": "^2.3.0", "@push.rocks/smartunique": "^3.0.9", - "@push.rocks/smartvpn": "1.13.0", + "@push.rocks/smartvpn": "1.14.0", "@push.rocks/taskbuffer": "^8.0.2", "@serve.zone/catalog": "^2.9.0", "@serve.zone/interfaces": "^5.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d48169..51f0ce7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -96,8 +96,8 @@ importers: specifier: ^3.0.9 version: 3.0.9 '@push.rocks/smartvpn': - specifier: 1.13.0 - version: 1.13.0 + specifier: 1.14.0 + version: 1.14.0 '@push.rocks/taskbuffer': specifier: ^8.0.2 version: 8.0.2 @@ -1330,8 +1330,8 @@ packages: '@push.rocks/smartversion@3.0.5': resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==} - '@push.rocks/smartvpn@1.13.0': - resolution: {integrity: sha512-oQY+GIvB9OZQMFEI/f4zwKwaUWPgG8Fsz8AGhPDedvH32jYNYEb9B957yRAROf7ndyQM/LThm7mN/5cx8ALyLw==} + '@push.rocks/smartvpn@1.14.0': + resolution: {integrity: sha512-zJmHiuLwY4OEN4jBVrJf1hAXpfO9f6Bulq/v1DrB16nR7VgE82KNqRLt0Wi/9PCsAUfmVJTvOf4yirnjBrEWQg==} '@push.rocks/smartwatch@6.4.0': resolution: {integrity: sha512-KDswRgE/siBmZRCsRA07MtW5oF4c9uQEBkwTGPIWneHzksbCDsvs/7agKFEL7WnNifLNwo8w1K1qoiVWkX1fvw==} @@ -6562,7 +6562,7 @@ snapshots: '@types/semver': 7.7.1 semver: 7.7.4 - '@push.rocks/smartvpn@1.13.0': + '@push.rocks/smartvpn@1.14.0': dependencies: '@push.rocks/smartpath': 6.0.0 '@push.rocks/smartrust': 1.3.2 diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 0408049..8dae047 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.15.0', + version: '11.16.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts/classes.dcrouter.ts b/ts/classes.dcrouter.ts index 1c02aa7..1d8b6e7 100644 --- a/ts/classes.dcrouter.ts +++ b/ts/classes.dcrouter.ts @@ -206,14 +206,21 @@ export interface IDcRouterOptions { dns?: string[]; /** Server endpoint hostname for client configs (e.g. 'vpn.example.com') */ serverEndpoint?: string; - /** Override forwarding mode. Default: auto-detect (tun if root, socket otherwise) */ - forwardingMode?: 'tun' | 'socket'; /** Pre-defined VPN clients created on startup */ clients?: Array<{ clientId: string; serverDefinedClientTags?: string[]; description?: string; }>; + /** Destination routing policy for VPN client traffic. + * Default in socket mode: { default: 'forceTarget', target: '127.0.0.1' } (all traffic → SmartProxy). + * Default in tun mode: not set (all traffic passes through). */ + destinationPolicy?: { + default: 'forceTarget' | 'block' | 'allow'; + target?: string; + allowList?: string[]; + blockList?: string[]; + }; }; } @@ -677,9 +684,8 @@ export class DcRouter { if (this.vpnManager && this.options.vpnConfig?.enabled) { const subnet = this.vpnManager.getSubnet(); const wgPort = this.options.vpnConfig.wgListenPort ?? 51820; - const mode = this.vpnManager.forwardingMode; const clientCount = this.vpnManager.listClients().length; - logger.log('info', `VPN Service: mode=${mode}, subnet=${subnet}, wg=:${wgPort}, clients=${clientCount}`); + logger.log('info', `VPN Service: subnet=${subnet}, wg=:${wgPort}, clients=${clientCount}`); } // Remote Ingress summary @@ -963,19 +969,14 @@ export class DcRouter { smartProxyConfig.proxyIPs = ['127.0.0.1']; } - // When VPN is in socket mode, the userspace NAT engine sends PP v2 headers - // on outbound connections to SmartProxy to preserve VPN client tunnel IPs. + // VPN uses socket mode with PP v2 — SmartProxy must accept proxy protocol from localhost if (this.options.vpnConfig?.enabled) { - const vpnForwardingMode = this.options.vpnConfig.forwardingMode - ?? (process.getuid?.() === 0 ? 'tun' : 'socket'); - if (vpnForwardingMode === 'socket') { - smartProxyConfig.acceptProxyProtocol = true; - if (!smartProxyConfig.proxyIPs) { - smartProxyConfig.proxyIPs = []; - } - if (!smartProxyConfig.proxyIPs.includes('127.0.0.1')) { - smartProxyConfig.proxyIPs.push('127.0.0.1'); - } + smartProxyConfig.acceptProxyProtocol = true; + if (!smartProxyConfig.proxyIPs) { + smartProxyConfig.proxyIPs = []; + } + if (!smartProxyConfig.proxyIPs.includes('127.0.0.1')) { + smartProxyConfig.proxyIPs.push('127.0.0.1'); } } @@ -2098,8 +2099,8 @@ export class DcRouter { wgListenPort: this.options.vpnConfig.wgListenPort, dns: this.options.vpnConfig.dns, serverEndpoint: this.options.vpnConfig.serverEndpoint, - forwardingMode: this.options.vpnConfig.forwardingMode, initialClients: this.options.vpnConfig.clients, + destinationPolicy: this.options.vpnConfig.destinationPolicy, onClientChanged: () => { // Re-apply routes so tag-based ipAllowLists get updated this.routeConfigManager?.applyRoutes(); diff --git a/ts/opsserver/handlers/vpn.handler.ts b/ts/opsserver/handlers/vpn.handler.ts index eaf7c69..c960941 100644 --- a/ts/opsserver/handlers/vpn.handler.ts +++ b/ts/opsserver/handlers/vpn.handler.ts @@ -48,7 +48,6 @@ export class VpnHandler { return { status: { running: false, - forwardingMode: 'socket' as const, subnet: vpnConfig?.subnet || '10.8.0.0/24', wgListenPort: vpnConfig?.wgListenPort ?? 51820, serverPublicKeys: null, @@ -62,7 +61,6 @@ export class VpnHandler { return { status: { running: manager.running, - forwardingMode: manager.forwardingMode, subnet: manager.getSubnet(), wgListenPort: vpnConfig?.wgListenPort ?? 51820, serverPublicKeys: manager.getServerPublicKeys(), diff --git a/ts/vpn/classes.vpn-manager.ts b/ts/vpn/classes.vpn-manager.ts index e4c4482..8b8d3e8 100644 --- a/ts/vpn/classes.vpn-manager.ts +++ b/ts/vpn/classes.vpn-manager.ts @@ -14,8 +14,6 @@ export interface IVpnManagerConfig { dns?: string[]; /** Server endpoint hostname for client configs (e.g. 'vpn.example.com') */ serverEndpoint?: string; - /** Override forwarding mode. Default: auto-detect (tun if root, socket otherwise) */ - forwardingMode?: 'tun' | 'socket'; /** Pre-defined VPN clients created on startup (idempotent — skips already-persisted clients) */ initialClients?: Array<{ clientId: string; @@ -24,6 +22,13 @@ export interface IVpnManagerConfig { }>; /** Called when clients are created/deleted/toggled — triggers route re-application */ onClientChanged?: () => void; + /** Destination routing policy override. Default: forceTarget to 127.0.0.1 */ + destinationPolicy?: { + default: 'forceTarget' | 'block' | 'allow'; + target?: string; + allowList?: string[]; + blockList?: string[]; + }; } interface IPersistedServerKeys { @@ -58,19 +63,10 @@ export class VpnManager { private vpnServer?: plugins.smartvpn.VpnServer; private clients: Map = new Map(); private serverKeys?: IPersistedServerKeys; - private _forwardingMode: 'tun' | 'socket'; constructor(storageManager: StorageManager, config: IVpnManagerConfig) { this.storageManager = storageManager; this.config = config; - // Auto-detect forwarding mode: tun if root, socket otherwise - this._forwardingMode = config.forwardingMode - ?? (process.getuid?.() === 0 ? 'tun' : 'socket'); - } - - /** The effective forwarding mode (tun or socket). */ - public get forwardingMode(): 'tun' | 'socket' { - return this._forwardingMode; } /** The VPN subnet CIDR. */ @@ -123,12 +119,14 @@ export class VpnManager { publicKey: this.serverKeys.noisePublicKey, subnet, dns: this.config.dns, - forwardingMode: this._forwardingMode, + forwardingMode: 'socket', transportMode: 'all', wgPrivateKey: this.serverKeys.wgPrivateKey, wgListenPort, clients: clientEntries, - socketForwardProxyProtocol: this._forwardingMode === 'socket', + socketForwardProxyProtocol: true, + destinationPolicy: this.config.destinationPolicy + ?? { default: 'forceTarget' as const, target: '127.0.0.1' }, }; await this.vpnServer.start(serverConfig); @@ -147,7 +145,7 @@ export class VpnManager { } } - logger.log('info', `VPN server started: mode=${this._forwardingMode}, subnet=${subnet}, wg=:${wgListenPort}, clients=${this.clients.size}`); + logger.log('info', `VPN server started: subnet=${subnet}, wg=:${wgListenPort}, clients=${this.clients.size}`); } /** diff --git a/ts_interfaces/data/vpn.ts b/ts_interfaces/data/vpn.ts index 75a31c2..df6a267 100644 --- a/ts_interfaces/data/vpn.ts +++ b/ts_interfaces/data/vpn.ts @@ -17,7 +17,6 @@ export interface IVpnClient { */ export interface IVpnServerStatus { running: boolean; - forwardingMode: 'tun' | 'socket'; subnet: string; wgListenPort: number; serverPublicKeys: { diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 0408049..8dae047 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@serve.zone/dcrouter', - version: '11.15.0', + version: '11.16.0', description: 'A multifaceted routing service handling mail and SMS delivery functions.' } diff --git a/ts_web/elements/ops-view-vpn.ts b/ts_web/elements/ops-view-vpn.ts index bdd5068..144dbc7 100644 --- a/ts_web/elements/ops-view-vpn.ts +++ b/ts_web/elements/ops-view-vpn.ts @@ -181,7 +181,7 @@ export class OpsViewVpn extends DeesElement { type: 'text', value: status?.running ? 'Running' : 'Stopped', icon: 'lucide:server', - description: status?.running ? `${status.forwardingMode} mode` : 'VPN server not running', + description: status?.running ? 'Active' : 'VPN server not running', color: status?.running ? '#10b981' : '#ef4444', }, ]; @@ -232,10 +232,6 @@ export class OpsViewVpn extends DeesElement { WireGuard Port ${status.wgListenPort} -
- Forwarding Mode - ${status.forwardingMode} -
${status.serverPublicKeys ? html`
WG Public Key