@push.rocks/smartvpn 1.0.3 → 1.2.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.
- package/dist_rust/smartvpn_daemon_linux_amd64 +0 -0
- package/dist_rust/smartvpn_daemon_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/smartvpn.classes.vpnclient.d.ts +10 -2
- package/dist_ts/smartvpn.classes.vpnclient.js +14 -2
- package/dist_ts/smartvpn.classes.vpnserver.d.ts +13 -1
- package/dist_ts/smartvpn.classes.vpnserver.js +23 -1
- package/dist_ts/smartvpn.interfaces.d.ts +70 -0
- package/package.json +1 -1
- package/readme.md +203 -68
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/smartvpn.classes.vpnclient.ts +17 -1
- package/ts/smartvpn.classes.vpnserver.ts +30 -0
- package/ts/smartvpn.interfaces.ts +63 -0
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartvpn',
|
|
6
|
-
version: '1.0
|
|
6
|
+
version: '1.2.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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxzQkFBc0I7SUFDNUIsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHlFQUF5RTtDQUN2RixDQUFBIn0=
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from './smartvpn.plugins.js';
|
|
2
|
-
import type { IVpnClientOptions, IVpnClientConfig, IVpnStatus, IVpnStatistics } from './smartvpn.interfaces.js';
|
|
2
|
+
import type { IVpnClientOptions, IVpnClientConfig, IVpnStatus, IVpnStatistics, IVpnConnectionQuality, IVpnMtuInfo } from './smartvpn.interfaces.js';
|
|
3
3
|
/**
|
|
4
4
|
* VPN Client — manages a smartvpn daemon in client mode.
|
|
5
5
|
*/
|
|
@@ -26,9 +26,17 @@ export declare class VpnClient extends plugins.events.EventEmitter {
|
|
|
26
26
|
*/
|
|
27
27
|
getStatus(): Promise<IVpnStatus>;
|
|
28
28
|
/**
|
|
29
|
-
* Get traffic statistics.
|
|
29
|
+
* Get traffic statistics (includes connection quality when connected).
|
|
30
30
|
*/
|
|
31
31
|
getStatistics(): Promise<IVpnStatistics>;
|
|
32
|
+
/**
|
|
33
|
+
* Get connection quality metrics (RTT, jitter, loss, link health).
|
|
34
|
+
*/
|
|
35
|
+
getConnectionQuality(): Promise<IVpnConnectionQuality>;
|
|
36
|
+
/**
|
|
37
|
+
* Get MTU information (overhead, effective MTU, oversized packet stats).
|
|
38
|
+
*/
|
|
39
|
+
getMtuInfo(): Promise<IVpnMtuInfo>;
|
|
32
40
|
/**
|
|
33
41
|
* Stop the daemon bridge.
|
|
34
42
|
*/
|
|
@@ -48,11 +48,23 @@ export class VpnClient extends plugins.events.EventEmitter {
|
|
|
48
48
|
return this.bridge.sendCommand('getStatus', {});
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* Get traffic statistics.
|
|
51
|
+
* Get traffic statistics (includes connection quality when connected).
|
|
52
52
|
*/
|
|
53
53
|
async getStatistics() {
|
|
54
54
|
return this.bridge.sendCommand('getStatistics', {});
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Get connection quality metrics (RTT, jitter, loss, link health).
|
|
58
|
+
*/
|
|
59
|
+
async getConnectionQuality() {
|
|
60
|
+
return this.bridge.sendCommand('getConnectionQuality', {});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get MTU information (overhead, effective MTU, oversized packet stats).
|
|
64
|
+
*/
|
|
65
|
+
async getMtuInfo() {
|
|
66
|
+
return this.bridge.sendCommand('getMtuInfo', {});
|
|
67
|
+
}
|
|
56
68
|
/**
|
|
57
69
|
* Stop the daemon bridge.
|
|
58
70
|
*/
|
|
@@ -66,4 +78,4 @@ export class VpnClient extends plugins.events.EventEmitter {
|
|
|
66
78
|
return this.bridge.running;
|
|
67
79
|
}
|
|
68
80
|
}
|
|
69
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
81
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR2cG4uY2xhc3Nlcy52cG5jbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHZwbi5jbGFzc2VzLnZwbmNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHVCQUF1QixDQUFDO0FBQ2pELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQVc1RDs7R0FFRztBQUNILE1BQU0sT0FBTyxTQUFVLFNBQVEsT0FBTyxDQUFDLE1BQU0sQ0FBQyxZQUFZO0lBSXhELFlBQVksT0FBMEI7UUFDcEMsS0FBSyxFQUFFLENBQUM7UUFDUixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksU0FBUyxDQUFxQjtZQUM5QyxTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVM7WUFDNUIsSUFBSSxFQUFFLFFBQVE7U0FDZixDQUFDLENBQUM7UUFFSCx3QkFBd0I7UUFDeEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBbUIsRUFBRSxNQUFxQixFQUFFLEVBQUU7WUFDcEUsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUN0QyxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLGFBQWEsRUFBRSxHQUFHLEVBQUU7WUFDakMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUMzQixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUM3QixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQXlCO1FBQzVDLE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQztRQUMxQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDVCxNQUFNLElBQUksS0FBSyxDQUFDLHVDQUF1QyxDQUFDLENBQUM7UUFDM0QsQ0FBQztRQUNELE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsU0FBUyxFQUFFLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFVBQVU7UUFDckIsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxZQUFZLEVBQUUsRUFBMkIsQ0FBQyxDQUFDO0lBQzNFLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxTQUFTO1FBQ3BCLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsV0FBVyxFQUFFLEVBQTJCLENBQUMsQ0FBQztJQUMzRSxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsYUFBYTtRQUN4QixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLGVBQWUsRUFBRSxFQUEyQixDQUFDLENBQUM7SUFDL0UsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLG9CQUFvQjtRQUMvQixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHNCQUFzQixFQUFFLEVBQTJCLENBQUMsQ0FBQztJQUN0RixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsVUFBVTtRQUNyQixPQUFPLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLFlBQVksRUFBRSxFQUEyQixDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVEOztPQUVHO0lBQ0ksSUFBSTtRQUNULElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDckIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsSUFBVyxPQUFPO1FBQ2hCLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUM7SUFDN0IsQ0FBQztDQUNGIn0=
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from './smartvpn.plugins.js';
|
|
2
|
-
import type { IVpnServerOptions, IVpnServerConfig, IVpnStatus, IVpnServerStatistics, IVpnClientInfo, IVpnKeypair } from './smartvpn.interfaces.js';
|
|
2
|
+
import type { IVpnServerOptions, IVpnServerConfig, IVpnStatus, IVpnServerStatistics, IVpnClientInfo, IVpnKeypair, IVpnClientTelemetry } from './smartvpn.interfaces.js';
|
|
3
3
|
/**
|
|
4
4
|
* VPN Server — manages a smartvpn daemon in server mode.
|
|
5
5
|
*/
|
|
@@ -35,6 +35,18 @@ export declare class VpnServer extends plugins.events.EventEmitter {
|
|
|
35
35
|
* Generate a new Noise keypair.
|
|
36
36
|
*/
|
|
37
37
|
generateKeypair(): Promise<IVpnKeypair>;
|
|
38
|
+
/**
|
|
39
|
+
* Set rate limit for a specific client.
|
|
40
|
+
*/
|
|
41
|
+
setClientRateLimit(clientId: string, rateBytesPerSec: number, burstBytes: number): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Remove rate limit for a specific client (unlimited).
|
|
44
|
+
*/
|
|
45
|
+
removeClientRateLimit(clientId: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Get telemetry for a specific client.
|
|
48
|
+
*/
|
|
49
|
+
getClientTelemetry(clientId: string): Promise<IVpnClientTelemetry>;
|
|
38
50
|
/**
|
|
39
51
|
* Stop the daemon bridge.
|
|
40
52
|
*/
|
|
@@ -69,6 +69,28 @@ export class VpnServer extends plugins.events.EventEmitter {
|
|
|
69
69
|
async generateKeypair() {
|
|
70
70
|
return this.bridge.sendCommand('generateKeypair', {});
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Set rate limit for a specific client.
|
|
74
|
+
*/
|
|
75
|
+
async setClientRateLimit(clientId, rateBytesPerSec, burstBytes) {
|
|
76
|
+
await this.bridge.sendCommand('setClientRateLimit', {
|
|
77
|
+
clientId,
|
|
78
|
+
rateBytesPerSec,
|
|
79
|
+
burstBytes,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Remove rate limit for a specific client (unlimited).
|
|
84
|
+
*/
|
|
85
|
+
async removeClientRateLimit(clientId) {
|
|
86
|
+
await this.bridge.sendCommand('removeClientRateLimit', { clientId });
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get telemetry for a specific client.
|
|
90
|
+
*/
|
|
91
|
+
async getClientTelemetry(clientId) {
|
|
92
|
+
return this.bridge.sendCommand('getClientTelemetry', { clientId });
|
|
93
|
+
}
|
|
72
94
|
/**
|
|
73
95
|
* Stop the daemon bridge.
|
|
74
96
|
*/
|
|
@@ -82,4 +104,4 @@ export class VpnServer extends plugins.events.EventEmitter {
|
|
|
82
104
|
return this.bridge.running;
|
|
83
105
|
}
|
|
84
106
|
}
|
|
85
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
107
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnR2cG4uY2xhc3Nlcy52cG5zZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydHZwbi5jbGFzc2VzLnZwbnNlcnZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHVCQUF1QixDQUFDO0FBQ2pELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQVk1RDs7R0FFRztBQUNILE1BQU0sT0FBTyxTQUFVLFNBQVEsT0FBTyxDQUFDLE1BQU0sQ0FBQyxZQUFZO0lBSXhELFlBQVksT0FBMEI7UUFDcEMsS0FBSyxFQUFFLENBQUM7UUFDUixJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sQ0FBQztRQUN2QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksU0FBUyxDQUFxQjtZQUM5QyxTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVM7WUFDNUIsSUFBSSxFQUFFLFFBQVE7U0FDZixDQUFDLENBQUM7UUFFSCx3QkFBd0I7UUFDeEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUMsSUFBbUIsRUFBRSxNQUFxQixFQUFFLEVBQUU7WUFDcEUsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUN0QyxDQUFDLENBQUMsQ0FBQztRQUNILElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLGFBQWEsRUFBRSxHQUFHLEVBQUU7WUFDakMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztRQUMzQixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBeUI7UUFDMUMsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzFDLElBQUksQ0FBQyxPQUFPLEVB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
|
|
@@ -47,6 +47,10 @@ export interface IVpnServerConfig {
|
|
|
47
47
|
keepaliveIntervalSecs?: number;
|
|
48
48
|
/** Enable NAT/masquerade for client traffic */
|
|
49
49
|
enableNat?: boolean;
|
|
50
|
+
/** Default rate limit for new clients (bytes/sec). Omit for unlimited. */
|
|
51
|
+
defaultRateLimitBytesPerSec?: number;
|
|
52
|
+
/** Default burst size for new clients (bytes). Omit for unlimited. */
|
|
53
|
+
defaultBurstBytes?: number;
|
|
50
54
|
}
|
|
51
55
|
export interface IVpnServerOptions {
|
|
52
56
|
transport: TVpnTransportOptions;
|
|
@@ -68,6 +72,7 @@ export interface IVpnStatistics {
|
|
|
68
72
|
keepalivesSent: number;
|
|
69
73
|
keepalivesReceived: number;
|
|
70
74
|
uptimeSeconds: number;
|
|
75
|
+
quality?: IVpnConnectionQuality;
|
|
71
76
|
}
|
|
72
77
|
export interface IVpnClientInfo {
|
|
73
78
|
clientId: string;
|
|
@@ -75,6 +80,12 @@ export interface IVpnClientInfo {
|
|
|
75
80
|
connectedSince: string;
|
|
76
81
|
bytesSent: number;
|
|
77
82
|
bytesReceived: number;
|
|
83
|
+
packetsDropped: number;
|
|
84
|
+
bytesDropped: number;
|
|
85
|
+
lastKeepaliveAt?: string;
|
|
86
|
+
keepalivesReceived: number;
|
|
87
|
+
rateLimitBytesPerSec?: number;
|
|
88
|
+
burstBytes?: number;
|
|
78
89
|
}
|
|
79
90
|
export interface IVpnServerStatistics extends IVpnStatistics {
|
|
80
91
|
activeClients: number;
|
|
@@ -84,6 +95,37 @@ export interface IVpnKeypair {
|
|
|
84
95
|
publicKey: string;
|
|
85
96
|
privateKey: string;
|
|
86
97
|
}
|
|
98
|
+
export type TVpnLinkHealth = 'healthy' | 'degraded' | 'critical';
|
|
99
|
+
export interface IVpnConnectionQuality {
|
|
100
|
+
srttMs: number;
|
|
101
|
+
jitterMs: number;
|
|
102
|
+
minRttMs: number;
|
|
103
|
+
maxRttMs: number;
|
|
104
|
+
lossRatio: number;
|
|
105
|
+
consecutiveTimeouts: number;
|
|
106
|
+
linkHealth: TVpnLinkHealth;
|
|
107
|
+
currentKeepaliveIntervalSecs: number;
|
|
108
|
+
}
|
|
109
|
+
export interface IVpnMtuInfo {
|
|
110
|
+
tunMtu: number;
|
|
111
|
+
effectiveMtu: number;
|
|
112
|
+
linkMtu: number;
|
|
113
|
+
overheadBytes: number;
|
|
114
|
+
oversizedPacketsDropped: number;
|
|
115
|
+
icmpTooBigSent: number;
|
|
116
|
+
}
|
|
117
|
+
export interface IVpnClientTelemetry {
|
|
118
|
+
clientId: string;
|
|
119
|
+
assignedIp: string;
|
|
120
|
+
lastKeepaliveAt?: string;
|
|
121
|
+
keepalivesReceived: number;
|
|
122
|
+
packetsDropped: number;
|
|
123
|
+
bytesDropped: number;
|
|
124
|
+
bytesReceived: number;
|
|
125
|
+
bytesSent: number;
|
|
126
|
+
rateLimitBytesPerSec?: number;
|
|
127
|
+
burstBytes?: number;
|
|
128
|
+
}
|
|
87
129
|
export type TVpnClientCommands = {
|
|
88
130
|
connect: {
|
|
89
131
|
params: {
|
|
@@ -105,6 +147,14 @@ export type TVpnClientCommands = {
|
|
|
105
147
|
params: Record<string, never>;
|
|
106
148
|
result: IVpnStatistics;
|
|
107
149
|
};
|
|
150
|
+
getConnectionQuality: {
|
|
151
|
+
params: Record<string, never>;
|
|
152
|
+
result: IVpnConnectionQuality;
|
|
153
|
+
};
|
|
154
|
+
getMtuInfo: {
|
|
155
|
+
params: Record<string, never>;
|
|
156
|
+
result: IVpnMtuInfo;
|
|
157
|
+
};
|
|
108
158
|
};
|
|
109
159
|
export type TVpnServerCommands = {
|
|
110
160
|
start: {
|
|
@@ -141,6 +191,26 @@ export type TVpnServerCommands = {
|
|
|
141
191
|
params: Record<string, never>;
|
|
142
192
|
result: IVpnKeypair;
|
|
143
193
|
};
|
|
194
|
+
setClientRateLimit: {
|
|
195
|
+
params: {
|
|
196
|
+
clientId: string;
|
|
197
|
+
rateBytesPerSec: number;
|
|
198
|
+
burstBytes: number;
|
|
199
|
+
};
|
|
200
|
+
result: void;
|
|
201
|
+
};
|
|
202
|
+
removeClientRateLimit: {
|
|
203
|
+
params: {
|
|
204
|
+
clientId: string;
|
|
205
|
+
};
|
|
206
|
+
result: void;
|
|
207
|
+
};
|
|
208
|
+
getClientTelemetry: {
|
|
209
|
+
params: {
|
|
210
|
+
clientId: string;
|
|
211
|
+
};
|
|
212
|
+
result: IVpnClientTelemetry;
|
|
213
|
+
};
|
|
144
214
|
};
|
|
145
215
|
export type TVpnPlatform = 'linux' | 'macos' | 'windows' | 'unknown';
|
|
146
216
|
export interface IVpnServiceUnit {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @push.rocks/smartvpn
|
|
2
2
|
|
|
3
|
-
A high-performance VPN
|
|
3
|
+
A high-performance VPN with a **TypeScript control plane** and a **Rust data plane daemon**. Manage VPN connections with clean, fully-typed APIs while all networking heavy lifting — encryption, tunneling, QoS, rate limiting — runs at native speed in Rust.
|
|
4
4
|
|
|
5
5
|
## Issue Reporting and Security
|
|
6
6
|
|
|
@@ -9,8 +9,6 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
9
9
|
## Install
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npm install @push.rocks/smartvpn
|
|
13
|
-
# or
|
|
14
12
|
pnpm install @push.rocks/smartvpn
|
|
15
13
|
```
|
|
16
14
|
|
|
@@ -18,17 +16,21 @@ pnpm install @push.rocks/smartvpn
|
|
|
18
16
|
|
|
19
17
|
```
|
|
20
18
|
TypeScript (control plane) Rust (data plane)
|
|
21
|
-
┌──────────────────────────┐
|
|
22
|
-
│ VpnClient / VpnServer │ │ smartvpn_daemon
|
|
23
|
-
│ └─ VpnBridge │──stdio/──▶ │ ├─ management (JSON IPC)
|
|
24
|
-
│ └─ RustBridge │ socket │ ├─ transport (WebSocket/TLS)
|
|
25
|
-
│ (smartrust) │ │ ├─ crypto (Noise NK +
|
|
26
|
-
└──────────────────────────┘ │ ├─ codec (binary framing)
|
|
27
|
-
│ ├─ keepalive (
|
|
28
|
-
│ ├─
|
|
29
|
-
│ ├─
|
|
30
|
-
│
|
|
31
|
-
|
|
19
|
+
┌──────────────────────────┐ ┌────────────────────────────────────┐
|
|
20
|
+
│ VpnClient / VpnServer │ │ smartvpn_daemon │
|
|
21
|
+
│ └─ VpnBridge │──stdio/──▶ │ ├─ management (JSON IPC) │
|
|
22
|
+
│ └─ RustBridge │ socket │ ├─ transport (WebSocket/TLS) │
|
|
23
|
+
│ (smartrust) │ │ ├─ crypto (Noise NK + XCha20) │
|
|
24
|
+
└──────────────────────────┘ │ ├─ codec (binary framing) │
|
|
25
|
+
│ ├─ keepalive (adaptive state FSM) │
|
|
26
|
+
│ ├─ telemetry (RTT/jitter/loss) │
|
|
27
|
+
│ ├─ qos (classify + priority Q) │
|
|
28
|
+
│ ├─ ratelimit (token bucket) │
|
|
29
|
+
│ ├─ mtu (overhead calc + ICMP) │
|
|
30
|
+
│ ├─ tunnel (TUN device) │
|
|
31
|
+
│ ├─ network (NAT/IP pool) │
|
|
32
|
+
│ └─ reconnect (exp. backoff) │
|
|
33
|
+
└────────────────────────────────────┘
|
|
32
34
|
```
|
|
33
35
|
|
|
34
36
|
**Key design decisions:**
|
|
@@ -37,8 +39,10 @@ TypeScript (control plane) Rust (data plane)
|
|
|
37
39
|
|----------|--------|-----|
|
|
38
40
|
| Transport | WebSocket over HTTPS | Works through Cloudflare and other terminating proxies |
|
|
39
41
|
| Encryption | Noise NK + XChaCha20-Poly1305 | Strong forward secrecy, large nonce space (no counter needed) |
|
|
40
|
-
| Keepalive |
|
|
41
|
-
|
|
|
42
|
+
| Keepalive | Adaptive app-level pings | Cloudflare drops WS pings; interval adapts to link health (10–60s) |
|
|
43
|
+
| QoS | Packet classification + priority queues | DNS/SSH/ICMP always drain first; bulk flows get deprioritized |
|
|
44
|
+
| Rate limiting | Per-client token bucket | Byte-granular, dynamically reconfigurable via IPC |
|
|
45
|
+
| IPC | JSON lines over stdio / Unix socket | `stdio` for dev, `socket` for production (daemon stays alive) |
|
|
42
46
|
| Binary protocol | `[type:1B][length:4B][payload:NB]` | Minimal overhead, easy to parse at wire speed |
|
|
43
47
|
|
|
44
48
|
## 🚀 Quick Start
|
|
@@ -48,15 +52,12 @@ TypeScript (control plane) Rust (data plane)
|
|
|
48
52
|
```typescript
|
|
49
53
|
import { VpnClient } from '@push.rocks/smartvpn';
|
|
50
54
|
|
|
51
|
-
// Development: spawn the Rust daemon as a child process
|
|
52
55
|
const client = new VpnClient({
|
|
53
56
|
transport: { transport: 'stdio' },
|
|
54
57
|
});
|
|
55
58
|
|
|
56
|
-
// Start the daemon bridge
|
|
57
59
|
await client.start();
|
|
58
60
|
|
|
59
|
-
// Connect to a VPN server
|
|
60
61
|
const { assignedIp } = await client.connect({
|
|
61
62
|
serverUrl: 'wss://vpn.example.com/tunnel',
|
|
62
63
|
serverPublicKey: 'BASE64_SERVER_PUBLIC_KEY',
|
|
@@ -67,15 +68,23 @@ const { assignedIp } = await client.connect({
|
|
|
67
68
|
|
|
68
69
|
console.log(`Connected! Assigned IP: ${assignedIp}`);
|
|
69
70
|
|
|
70
|
-
//
|
|
71
|
-
const
|
|
72
|
-
console.log(
|
|
73
|
-
|
|
74
|
-
//
|
|
71
|
+
// Connection quality (adaptive keepalive + telemetry)
|
|
72
|
+
const quality = await client.getConnectionQuality();
|
|
73
|
+
console.log(quality);
|
|
74
|
+
// {
|
|
75
|
+
// srttMs: 42.5, jitterMs: 3.2, minRttMs: 38.0, maxRttMs: 67.0,
|
|
76
|
+
// lossRatio: 0.0, consecutiveTimeouts: 0,
|
|
77
|
+
// linkHealth: 'healthy', currentKeepaliveIntervalSecs: 60
|
|
78
|
+
// }
|
|
79
|
+
|
|
80
|
+
// MTU info
|
|
81
|
+
const mtu = await client.getMtuInfo();
|
|
82
|
+
console.log(mtu);
|
|
83
|
+
// { tunMtu: 1420, effectiveMtu: 1421, linkMtu: 1500, overheadBytes: 79, ... }
|
|
84
|
+
|
|
85
|
+
// Traffic stats (includes quality snapshot)
|
|
75
86
|
const stats = await client.getStatistics();
|
|
76
|
-
console.log(stats); // { bytesSent, bytesReceived, packetsSent, ... }
|
|
77
87
|
|
|
78
|
-
// Disconnect
|
|
79
88
|
await client.disconnect();
|
|
80
89
|
client.stop();
|
|
81
90
|
```
|
|
@@ -89,33 +98,44 @@ const server = new VpnServer({
|
|
|
89
98
|
transport: { transport: 'stdio' },
|
|
90
99
|
});
|
|
91
100
|
|
|
92
|
-
//
|
|
101
|
+
// Generate a Noise keypair first
|
|
102
|
+
await server.start();
|
|
103
|
+
// If you don't have keys yet:
|
|
104
|
+
const keypair = await server.generateKeypair();
|
|
105
|
+
|
|
106
|
+
// Start the VPN listener (or pass config to start() directly)
|
|
93
107
|
await server.start({
|
|
94
108
|
listenAddr: '0.0.0.0:443',
|
|
95
|
-
privateKey:
|
|
96
|
-
publicKey:
|
|
109
|
+
privateKey: keypair.privateKey,
|
|
110
|
+
publicKey: keypair.publicKey,
|
|
97
111
|
subnet: '10.8.0.0/24',
|
|
98
112
|
dns: ['1.1.1.1'],
|
|
99
113
|
mtu: 1420,
|
|
100
114
|
enableNat: true,
|
|
115
|
+
// Optional: default rate limit for all new clients
|
|
116
|
+
defaultRateLimitBytesPerSec: 10_000_000, // 10 MB/s
|
|
117
|
+
defaultBurstBytes: 20_000_000, // 20 MB burst
|
|
101
118
|
});
|
|
102
119
|
|
|
103
|
-
// Generate a Noise keypair
|
|
104
|
-
const keypair = await server.generateKeypair();
|
|
105
|
-
console.log(keypair); // { publicKey: '...', privateKey: '...' }
|
|
106
|
-
|
|
107
120
|
// List connected clients
|
|
108
121
|
const clients = await server.listClients();
|
|
109
|
-
// [{ clientId, assignedIp, connectedSince, bytesSent, bytesReceived }]
|
|
110
122
|
|
|
111
|
-
//
|
|
112
|
-
await server.
|
|
123
|
+
// Per-client rate limiting (live, no reconnect needed)
|
|
124
|
+
await server.setClientRateLimit('client-id', 5_000_000, 10_000_000);
|
|
125
|
+
await server.removeClientRateLimit('client-id'); // unlimited
|
|
126
|
+
|
|
127
|
+
// Per-client telemetry
|
|
128
|
+
const telemetry = await server.getClientTelemetry('client-id');
|
|
129
|
+
console.log(telemetry);
|
|
130
|
+
// {
|
|
131
|
+
// clientId, assignedIp, lastKeepaliveAt, keepalivesReceived,
|
|
132
|
+
// packetsDropped, bytesDropped, bytesReceived, bytesSent,
|
|
133
|
+
// rateLimitBytesPerSec, burstBytes
|
|
134
|
+
// }
|
|
113
135
|
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
// { bytesSent, bytesReceived, activeClients, totalConnections, ... }
|
|
136
|
+
// Kick a client
|
|
137
|
+
await server.disconnectClient('client-id');
|
|
117
138
|
|
|
118
|
-
// Stop
|
|
119
139
|
await server.stopServer();
|
|
120
140
|
server.stop();
|
|
121
141
|
```
|
|
@@ -151,7 +171,9 @@ When using socket transport, `client.stop()` closes the socket but **does not ki
|
|
|
151
171
|
| `connect(config?)` | `Promise<{ assignedIp }>` | Connect to VPN server |
|
|
152
172
|
| `disconnect()` | `Promise<void>` | Disconnect from VPN |
|
|
153
173
|
| `getStatus()` | `Promise<IVpnStatus>` | Current connection state |
|
|
154
|
-
| `getStatistics()` | `Promise<IVpnStatistics>` | Traffic
|
|
174
|
+
| `getStatistics()` | `Promise<IVpnStatistics>` | Traffic stats + connection quality |
|
|
175
|
+
| `getConnectionQuality()` | `Promise<IVpnConnectionQuality>` | RTT, jitter, loss, link health |
|
|
176
|
+
| `getMtuInfo()` | `Promise<IVpnMtuInfo>` | MTU info and overhead breakdown |
|
|
155
177
|
| `stop()` | `void` | Kill/close the daemon bridge |
|
|
156
178
|
| `running` | `boolean` | Whether bridge is active |
|
|
157
179
|
|
|
@@ -163,9 +185,12 @@ When using socket transport, `client.stop()` closes the socket but **does not ki
|
|
|
163
185
|
| `stopServer()` | `Promise<void>` | Stop the VPN server |
|
|
164
186
|
| `getStatus()` | `Promise<IVpnStatus>` | Server connection state |
|
|
165
187
|
| `getStatistics()` | `Promise<IVpnServerStatistics>` | Server stats (includes client counts) |
|
|
166
|
-
| `listClients()` | `Promise<IVpnClientInfo[]>` | Connected clients |
|
|
188
|
+
| `listClients()` | `Promise<IVpnClientInfo[]>` | Connected clients with QoS stats |
|
|
167
189
|
| `disconnectClient(id)` | `Promise<void>` | Kick a client |
|
|
168
190
|
| `generateKeypair()` | `Promise<IVpnKeypair>` | Generate Noise NK keypair |
|
|
191
|
+
| `setClientRateLimit(id, rate, burst)` | `Promise<void>` | Set per-client rate limit (bytes/sec) |
|
|
192
|
+
| `removeClientRateLimit(id)` | `Promise<void>` | Remove rate limit (unlimited) |
|
|
193
|
+
| `getClientTelemetry(id)` | `Promise<IVpnClientTelemetry>` | Per-client telemetry + drop stats |
|
|
169
194
|
| `stop()` | `void` | Kill/close the daemon bridge |
|
|
170
195
|
|
|
171
196
|
### `VpnConfig`
|
|
@@ -191,26 +216,23 @@ Generate system service units for the daemon:
|
|
|
191
216
|
```typescript
|
|
192
217
|
import { VpnInstaller } from '@push.rocks/smartvpn';
|
|
193
218
|
|
|
194
|
-
// Auto-detect platform
|
|
195
219
|
const platform = VpnInstaller.detectPlatform(); // 'linux' | 'macos' | 'windows' | 'unknown'
|
|
196
220
|
|
|
197
|
-
//
|
|
221
|
+
// Linux (systemd)
|
|
198
222
|
const unit = VpnInstaller.generateSystemdUnit({
|
|
199
223
|
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
|
200
224
|
socketPath: '/var/run/smartvpn.sock',
|
|
201
225
|
mode: 'server',
|
|
202
226
|
});
|
|
203
|
-
// unit.content = full systemd .service file
|
|
204
|
-
// unit.installPath = '/etc/systemd/system/smartvpn-server.service'
|
|
205
227
|
|
|
206
|
-
//
|
|
228
|
+
// macOS (launchd)
|
|
207
229
|
const plist = VpnInstaller.generateLaunchdPlist({
|
|
208
230
|
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
|
209
231
|
socketPath: '/var/run/smartvpn.sock',
|
|
210
232
|
mode: 'client',
|
|
211
233
|
});
|
|
212
234
|
|
|
213
|
-
// Auto-detect
|
|
235
|
+
// Auto-detect platform
|
|
214
236
|
const serviceUnit = VpnInstaller.generateServiceUnit({
|
|
215
237
|
binaryPath: '/usr/local/bin/smartvpn_daemon',
|
|
216
238
|
socketPath: '/var/run/smartvpn.sock',
|
|
@@ -223,8 +245,6 @@ const serviceUnit = VpnInstaller.generateServiceUnit({
|
|
|
223
245
|
Both `VpnClient` and `VpnServer` extend `EventEmitter`:
|
|
224
246
|
|
|
225
247
|
```typescript
|
|
226
|
-
client.on('status', (status) => { /* IVpnStatus */ });
|
|
227
|
-
client.on('error', (err) => { /* { message, code? } */ });
|
|
228
248
|
client.on('exit', ({ code, signal }) => { /* daemon exited */ });
|
|
229
249
|
client.on('reconnected', () => { /* socket reconnected */ });
|
|
230
250
|
|
|
@@ -232,13 +252,84 @@ server.on('client-connected', (info) => { /* IVpnClientInfo */ });
|
|
|
232
252
|
server.on('client-disconnected', ({ clientId, reason }) => { /* ... */ });
|
|
233
253
|
```
|
|
234
254
|
|
|
255
|
+
## 📊 QoS System
|
|
256
|
+
|
|
257
|
+
The Rust daemon includes a full QoS stack that operates on decrypted IP packets:
|
|
258
|
+
|
|
259
|
+
### Adaptive Keepalive
|
|
260
|
+
|
|
261
|
+
The keepalive system automatically adjusts its interval based on connection quality:
|
|
262
|
+
|
|
263
|
+
| Link Health | Keepalive Interval | Triggered When |
|
|
264
|
+
|-------------|-------------------|----------------|
|
|
265
|
+
| 🟢 Healthy | 60s | Jitter < 30ms, loss < 2%, no timeouts |
|
|
266
|
+
| 🟡 Degraded | 30s | Jitter > 50ms, loss > 5%, or 1+ timeout |
|
|
267
|
+
| 🔴 Critical | 10s | Loss > 20% or 2+ consecutive timeouts |
|
|
268
|
+
|
|
269
|
+
State transitions include hysteresis (3 consecutive good checks to upgrade, 2 to recover) to prevent flapping. Dead peer detection fires after 3 consecutive timeouts in Critical state.
|
|
270
|
+
|
|
271
|
+
### Packet Classification
|
|
272
|
+
|
|
273
|
+
IP packets are classified into three priority levels by inspecting headers (no deep packet inspection):
|
|
274
|
+
|
|
275
|
+
| Priority | Traffic |
|
|
276
|
+
|----------|---------|
|
|
277
|
+
| **High** | ICMP, DNS (port 53), SSH (port 22), small packets (< 128 bytes) |
|
|
278
|
+
| **Normal** | Everything else |
|
|
279
|
+
| **Low** | Bulk flows exceeding 1 MB within a 60s window |
|
|
280
|
+
|
|
281
|
+
Priority channels drain with biased `tokio::select!` — high-priority packets always go first.
|
|
282
|
+
|
|
283
|
+
### Smart Packet Dropping
|
|
284
|
+
|
|
285
|
+
Under backpressure, packets are dropped intelligently:
|
|
286
|
+
|
|
287
|
+
1. **Low** queue full → drop silently
|
|
288
|
+
2. **Normal** queue full → drop
|
|
289
|
+
3. **High** queue full → wait 5ms, then drop as last resort
|
|
290
|
+
|
|
291
|
+
Drop statistics are tracked per priority level and exposed via telemetry.
|
|
292
|
+
|
|
293
|
+
### Per-Client Rate Limiting
|
|
294
|
+
|
|
295
|
+
Token bucket algorithm with byte granularity:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
// Set: 10 MB/s sustained, 20 MB burst
|
|
299
|
+
await server.setClientRateLimit('client-id', 10_000_000, 20_000_000);
|
|
300
|
+
|
|
301
|
+
// Check drops via telemetry
|
|
302
|
+
const t = await server.getClientTelemetry('client-id');
|
|
303
|
+
console.log(`Dropped: ${t.packetsDropped} packets, ${t.bytesDropped} bytes`);
|
|
304
|
+
|
|
305
|
+
// Remove limit
|
|
306
|
+
await server.removeClientRateLimit('client-id');
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Rate limits can be changed live without disconnecting the client.
|
|
310
|
+
|
|
311
|
+
### Path MTU
|
|
312
|
+
|
|
313
|
+
Tunnel overhead is calculated precisely:
|
|
314
|
+
|
|
315
|
+
| Layer | Bytes |
|
|
316
|
+
|-------|-------|
|
|
317
|
+
| IP header | 20 |
|
|
318
|
+
| TCP header (with timestamps) | 32 |
|
|
319
|
+
| WebSocket framing | 6 |
|
|
320
|
+
| VPN frame header | 5 |
|
|
321
|
+
| Noise AEAD tag | 16 |
|
|
322
|
+
| **Total overhead** | **79** |
|
|
323
|
+
|
|
324
|
+
For a standard 1500-byte Ethernet link, effective TUN MTU = **1421 bytes**. The default TUN MTU of 1420 is conservative and correct. Oversized packets get an ICMP "Fragmentation Needed" (Type 3, Code 4) written back into the TUN, so the source TCP adjusts its MSS automatically.
|
|
325
|
+
|
|
235
326
|
## 🔐 Security Model
|
|
236
327
|
|
|
237
328
|
The VPN uses a **Noise NK** handshake pattern:
|
|
238
329
|
|
|
239
330
|
1. **NK** = client does **N**ot authenticate, but **K**nows the server's static public key
|
|
240
|
-
2. The client generates an ephemeral keypair, performs `e, es` (
|
|
241
|
-
3. Server responds with `e, ee` (
|
|
331
|
+
2. The client generates an ephemeral keypair, performs `e, es` (DH with server's static key)
|
|
332
|
+
3. Server responds with `e, ee` (DH with both ephemeral keys)
|
|
242
333
|
4. Result: forward-secret transport keys derived from both DH operations
|
|
243
334
|
|
|
244
335
|
Post-handshake, all IP packets are encrypted with **XChaCha20-Poly1305**:
|
|
@@ -261,8 +352,8 @@ Inside the WebSocket tunnel, packets use a simple binary framing:
|
|
|
261
352
|
| `HandshakeInit` | `0x01` | Client → Server handshake |
|
|
262
353
|
| `HandshakeResp` | `0x02` | Server → Client handshake |
|
|
263
354
|
| `IpPacket` | `0x10` | Encrypted IP packet |
|
|
264
|
-
| `Keepalive` | `0x20` | App-level ping |
|
|
265
|
-
| `KeepaliveAck` | `0x21` | App-level pong |
|
|
355
|
+
| `Keepalive` | `0x20` | App-level ping (8-byte timestamp payload) |
|
|
356
|
+
| `KeepaliveAck` | `0x21` | App-level pong (echoes timestamp for RTT) |
|
|
266
357
|
| `SessionResume` | `0x30` | Resume a dropped session |
|
|
267
358
|
| `SessionResumeOk` | `0x31` | Resume accepted |
|
|
268
359
|
| `SessionResumeErr` | `0x32` | Resume rejected |
|
|
@@ -270,8 +361,6 @@ Inside the WebSocket tunnel, packets use a simple binary framing:
|
|
|
270
361
|
|
|
271
362
|
## 🛠️ Rust Daemon CLI
|
|
272
363
|
|
|
273
|
-
The Rust binary supports several modes:
|
|
274
|
-
|
|
275
364
|
```bash
|
|
276
365
|
# Development: stdio management (JSON lines on stdin/stdout)
|
|
277
366
|
smartvpn_daemon --management --mode client
|
|
@@ -290,16 +379,14 @@ smartvpn_daemon --generate-keypair
|
|
|
290
379
|
# Install dependencies
|
|
291
380
|
pnpm install
|
|
292
381
|
|
|
293
|
-
# Build TypeScript + cross-compile Rust
|
|
382
|
+
# Build TypeScript + cross-compile Rust (amd64 + arm64)
|
|
294
383
|
pnpm build
|
|
295
384
|
|
|
296
385
|
# Build Rust only (debug)
|
|
297
386
|
cd rust && cargo build
|
|
298
387
|
|
|
299
|
-
# Run Rust
|
|
388
|
+
# Run all tests (71 Rust + 32 TypeScript)
|
|
300
389
|
cd rust && cargo test
|
|
301
|
-
|
|
302
|
-
# Run TypeScript tests
|
|
303
390
|
pnpm test
|
|
304
391
|
```
|
|
305
392
|
|
|
@@ -323,25 +410,27 @@ type TVpnTransportOptions =
|
|
|
323
410
|
|
|
324
411
|
// Client config
|
|
325
412
|
interface IVpnClientConfig {
|
|
326
|
-
serverUrl: string;
|
|
327
|
-
serverPublicKey: string;
|
|
413
|
+
serverUrl: string;
|
|
414
|
+
serverPublicKey: string;
|
|
328
415
|
dns?: string[];
|
|
329
|
-
mtu?: number;
|
|
330
|
-
keepaliveIntervalSecs?: number;
|
|
416
|
+
mtu?: number;
|
|
417
|
+
keepaliveIntervalSecs?: number;
|
|
331
418
|
}
|
|
332
419
|
|
|
333
420
|
// Server config
|
|
334
421
|
interface IVpnServerConfig {
|
|
335
|
-
listenAddr: string;
|
|
336
|
-
privateKey: string;
|
|
337
|
-
publicKey: string;
|
|
338
|
-
subnet: string;
|
|
422
|
+
listenAddr: string;
|
|
423
|
+
privateKey: string;
|
|
424
|
+
publicKey: string;
|
|
425
|
+
subnet: string;
|
|
339
426
|
tlsCert?: string;
|
|
340
427
|
tlsKey?: string;
|
|
341
428
|
dns?: string[];
|
|
342
429
|
mtu?: number;
|
|
343
430
|
keepaliveIntervalSecs?: number;
|
|
344
431
|
enableNat?: boolean;
|
|
432
|
+
defaultRateLimitBytesPerSec?: number;
|
|
433
|
+
defaultBurstBytes?: number;
|
|
345
434
|
}
|
|
346
435
|
|
|
347
436
|
// Status
|
|
@@ -365,6 +454,7 @@ interface IVpnStatistics {
|
|
|
365
454
|
keepalivesSent: number;
|
|
366
455
|
keepalivesReceived: number;
|
|
367
456
|
uptimeSeconds: number;
|
|
457
|
+
quality?: IVpnConnectionQuality;
|
|
368
458
|
}
|
|
369
459
|
|
|
370
460
|
interface IVpnServerStatistics extends IVpnStatistics {
|
|
@@ -372,12 +462,57 @@ interface IVpnServerStatistics extends IVpnStatistics {
|
|
|
372
462
|
totalConnections: number;
|
|
373
463
|
}
|
|
374
464
|
|
|
465
|
+
// Connection quality (QoS)
|
|
466
|
+
type TVpnLinkHealth = 'healthy' | 'degraded' | 'critical';
|
|
467
|
+
|
|
468
|
+
interface IVpnConnectionQuality {
|
|
469
|
+
srttMs: number;
|
|
470
|
+
jitterMs: number;
|
|
471
|
+
minRttMs: number;
|
|
472
|
+
maxRttMs: number;
|
|
473
|
+
lossRatio: number;
|
|
474
|
+
consecutiveTimeouts: number;
|
|
475
|
+
linkHealth: TVpnLinkHealth;
|
|
476
|
+
currentKeepaliveIntervalSecs: number;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// MTU info
|
|
480
|
+
interface IVpnMtuInfo {
|
|
481
|
+
tunMtu: number;
|
|
482
|
+
effectiveMtu: number;
|
|
483
|
+
linkMtu: number;
|
|
484
|
+
overheadBytes: number;
|
|
485
|
+
oversizedPacketsDropped: number;
|
|
486
|
+
icmpTooBigSent: number;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Client info (with QoS fields)
|
|
375
490
|
interface IVpnClientInfo {
|
|
376
491
|
clientId: string;
|
|
377
492
|
assignedIp: string;
|
|
378
493
|
connectedSince: string;
|
|
379
494
|
bytesSent: number;
|
|
380
495
|
bytesReceived: number;
|
|
496
|
+
packetsDropped: number;
|
|
497
|
+
bytesDropped: number;
|
|
498
|
+
lastKeepaliveAt?: string;
|
|
499
|
+
keepalivesReceived: number;
|
|
500
|
+
rateLimitBytesPerSec?: number;
|
|
501
|
+
burstBytes?: number;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Per-client telemetry
|
|
505
|
+
interface IVpnClientTelemetry {
|
|
506
|
+
clientId: string;
|
|
507
|
+
assignedIp: string;
|
|
508
|
+
lastKeepaliveAt?: string;
|
|
509
|
+
keepalivesReceived: number;
|
|
510
|
+
packetsDropped: number;
|
|
511
|
+
bytesDropped: number;
|
|
512
|
+
bytesReceived: number;
|
|
513
|
+
bytesSent: number;
|
|
514
|
+
rateLimitBytesPerSec?: number;
|
|
515
|
+
burstBytes?: number;
|
|
381
516
|
}
|
|
382
517
|
|
|
383
518
|
interface IVpnKeypair {
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -5,6 +5,8 @@ import type {
|
|
|
5
5
|
IVpnClientConfig,
|
|
6
6
|
IVpnStatus,
|
|
7
7
|
IVpnStatistics,
|
|
8
|
+
IVpnConnectionQuality,
|
|
9
|
+
IVpnMtuInfo,
|
|
8
10
|
TVpnClientCommands,
|
|
9
11
|
} from './smartvpn.interfaces.js';
|
|
10
12
|
|
|
@@ -65,12 +67,26 @@ export class VpnClient extends plugins.events.EventEmitter {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
|
-
* Get traffic statistics.
|
|
70
|
+
* Get traffic statistics (includes connection quality when connected).
|
|
69
71
|
*/
|
|
70
72
|
public async getStatistics(): Promise<IVpnStatistics> {
|
|
71
73
|
return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Get connection quality metrics (RTT, jitter, loss, link health).
|
|
78
|
+
*/
|
|
79
|
+
public async getConnectionQuality(): Promise<IVpnConnectionQuality> {
|
|
80
|
+
return this.bridge.sendCommand('getConnectionQuality', {} as Record<string, never>);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get MTU information (overhead, effective MTU, oversized packet stats).
|
|
85
|
+
*/
|
|
86
|
+
public async getMtuInfo(): Promise<IVpnMtuInfo> {
|
|
87
|
+
return this.bridge.sendCommand('getMtuInfo', {} as Record<string, never>);
|
|
88
|
+
}
|
|
89
|
+
|
|
74
90
|
/**
|
|
75
91
|
* Stop the daemon bridge.
|
|
76
92
|
*/
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
IVpnServerStatistics,
|
|
8
8
|
IVpnClientInfo,
|
|
9
9
|
IVpnKeypair,
|
|
10
|
+
IVpnClientTelemetry,
|
|
10
11
|
TVpnServerCommands,
|
|
11
12
|
} from './smartvpn.interfaces.js';
|
|
12
13
|
|
|
@@ -91,6 +92,35 @@ export class VpnServer extends plugins.events.EventEmitter {
|
|
|
91
92
|
return this.bridge.sendCommand('generateKeypair', {} as Record<string, never>);
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Set rate limit for a specific client.
|
|
97
|
+
*/
|
|
98
|
+
public async setClientRateLimit(
|
|
99
|
+
clientId: string,
|
|
100
|
+
rateBytesPerSec: number,
|
|
101
|
+
burstBytes: number,
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
await this.bridge.sendCommand('setClientRateLimit', {
|
|
104
|
+
clientId,
|
|
105
|
+
rateBytesPerSec,
|
|
106
|
+
burstBytes,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Remove rate limit for a specific client (unlimited).
|
|
112
|
+
*/
|
|
113
|
+
public async removeClientRateLimit(clientId: string): Promise<void> {
|
|
114
|
+
await this.bridge.sendCommand('removeClientRateLimit', { clientId });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get telemetry for a specific client.
|
|
119
|
+
*/
|
|
120
|
+
public async getClientTelemetry(clientId: string): Promise<IVpnClientTelemetry> {
|
|
121
|
+
return this.bridge.sendCommand('getClientTelemetry', { clientId });
|
|
122
|
+
}
|
|
123
|
+
|
|
94
124
|
/**
|
|
95
125
|
* Stop the daemon bridge.
|
|
96
126
|
*/
|
|
@@ -64,6 +64,10 @@ export interface IVpnServerConfig {
|
|
|
64
64
|
keepaliveIntervalSecs?: number;
|
|
65
65
|
/** Enable NAT/masquerade for client traffic */
|
|
66
66
|
enableNat?: boolean;
|
|
67
|
+
/** Default rate limit for new clients (bytes/sec). Omit for unlimited. */
|
|
68
|
+
defaultRateLimitBytesPerSec?: number;
|
|
69
|
+
/** Default burst size for new clients (bytes). Omit for unlimited. */
|
|
70
|
+
defaultBurstBytes?: number;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
export interface IVpnServerOptions {
|
|
@@ -99,6 +103,7 @@ export interface IVpnStatistics {
|
|
|
99
103
|
keepalivesSent: number;
|
|
100
104
|
keepalivesReceived: number;
|
|
101
105
|
uptimeSeconds: number;
|
|
106
|
+
quality?: IVpnConnectionQuality;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
export interface IVpnClientInfo {
|
|
@@ -107,6 +112,12 @@ export interface IVpnClientInfo {
|
|
|
107
112
|
connectedSince: string;
|
|
108
113
|
bytesSent: number;
|
|
109
114
|
bytesReceived: number;
|
|
115
|
+
packetsDropped: number;
|
|
116
|
+
bytesDropped: number;
|
|
117
|
+
lastKeepaliveAt?: string;
|
|
118
|
+
keepalivesReceived: number;
|
|
119
|
+
rateLimitBytesPerSec?: number;
|
|
120
|
+
burstBytes?: number;
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
export interface IVpnServerStatistics extends IVpnStatistics {
|
|
@@ -119,6 +130,53 @@ export interface IVpnKeypair {
|
|
|
119
130
|
privateKey: string;
|
|
120
131
|
}
|
|
121
132
|
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// QoS: Connection quality
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
export type TVpnLinkHealth = 'healthy' | 'degraded' | 'critical';
|
|
138
|
+
|
|
139
|
+
export interface IVpnConnectionQuality {
|
|
140
|
+
srttMs: number;
|
|
141
|
+
jitterMs: number;
|
|
142
|
+
minRttMs: number;
|
|
143
|
+
maxRttMs: number;
|
|
144
|
+
lossRatio: number;
|
|
145
|
+
consecutiveTimeouts: number;
|
|
146
|
+
linkHealth: TVpnLinkHealth;
|
|
147
|
+
currentKeepaliveIntervalSecs: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// QoS: MTU info
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
export interface IVpnMtuInfo {
|
|
155
|
+
tunMtu: number;
|
|
156
|
+
effectiveMtu: number;
|
|
157
|
+
linkMtu: number;
|
|
158
|
+
overheadBytes: number;
|
|
159
|
+
oversizedPacketsDropped: number;
|
|
160
|
+
icmpTooBigSent: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// QoS: Client telemetry (server-side per-client)
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
export interface IVpnClientTelemetry {
|
|
168
|
+
clientId: string;
|
|
169
|
+
assignedIp: string;
|
|
170
|
+
lastKeepaliveAt?: string;
|
|
171
|
+
keepalivesReceived: number;
|
|
172
|
+
packetsDropped: number;
|
|
173
|
+
bytesDropped: number;
|
|
174
|
+
bytesReceived: number;
|
|
175
|
+
bytesSent: number;
|
|
176
|
+
rateLimitBytesPerSec?: number;
|
|
177
|
+
burstBytes?: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
122
180
|
// ============================================================================
|
|
123
181
|
// IPC Command maps (used by smartrust RustBridge<TCommands>)
|
|
124
182
|
// ============================================================================
|
|
@@ -128,6 +186,8 @@ export type TVpnClientCommands = {
|
|
|
128
186
|
disconnect: { params: Record<string, never>; result: void };
|
|
129
187
|
getStatus: { params: Record<string, never>; result: IVpnStatus };
|
|
130
188
|
getStatistics: { params: Record<string, never>; result: IVpnStatistics };
|
|
189
|
+
getConnectionQuality: { params: Record<string, never>; result: IVpnConnectionQuality };
|
|
190
|
+
getMtuInfo: { params: Record<string, never>; result: IVpnMtuInfo };
|
|
131
191
|
};
|
|
132
192
|
|
|
133
193
|
export type TVpnServerCommands = {
|
|
@@ -138,6 +198,9 @@ export type TVpnServerCommands = {
|
|
|
138
198
|
listClients: { params: Record<string, never>; result: { clients: IVpnClientInfo[] } };
|
|
139
199
|
disconnectClient: { params: { clientId: string }; result: void };
|
|
140
200
|
generateKeypair: { params: Record<string, never>; result: IVpnKeypair };
|
|
201
|
+
setClientRateLimit: { params: { clientId: string; rateBytesPerSec: number; burstBytes: number }; result: void };
|
|
202
|
+
removeClientRateLimit: { params: { clientId: string }; result: void };
|
|
203
|
+
getClientTelemetry: { params: { clientId: string }; result: IVpnClientTelemetry };
|
|
141
204
|
};
|
|
142
205
|
|
|
143
206
|
// ============================================================================
|