@serve.zone/remoteingress 4.14.2 → 4.15.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/.smartconfig.json +14 -14
- package/dist_rust/remoteingress-bin_linux_amd64 +0 -0
- package/dist_rust/remoteingress-bin_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.remoteingressedge.d.ts +10 -0
- package/dist_ts/classes.remoteingressedge.js +89 -1
- package/dist_ts/classes.remoteingresshub.d.ts +23 -0
- package/dist_ts/classes.remoteingresshub.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +2 -1
- package/readme.md +179 -59
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.remoteingressedge.ts +94 -0
- package/ts/classes.remoteingresshub.ts +27 -2
- package/ts/plugins.ts +2 -1
package/.smartconfig.json
CHANGED
|
@@ -11,26 +11,26 @@
|
|
|
11
11
|
"githost": "code.foss.global",
|
|
12
12
|
"gitscope": "serve.zone",
|
|
13
13
|
"gitrepo": "remoteingress",
|
|
14
|
-
"description": "
|
|
14
|
+
"description": "Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.",
|
|
15
15
|
"npmPackagename": "@serve.zone/remoteingress",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"projectDomain": "serve.zone",
|
|
18
18
|
"keywords": [
|
|
19
19
|
"remote access",
|
|
20
|
-
"
|
|
21
|
-
"network
|
|
22
|
-
"
|
|
23
|
-
"
|
|
20
|
+
"ingress tunnel",
|
|
21
|
+
"network edge",
|
|
22
|
+
"PROXY protocol",
|
|
23
|
+
"multiplexed tunnel",
|
|
24
|
+
"TCP proxy",
|
|
25
|
+
"TLS tunnel",
|
|
26
|
+
"QUIC transport",
|
|
27
|
+
"UDP tunneling",
|
|
24
28
|
"serve.zone stack",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"TLS/SSL certificates",
|
|
31
|
-
"development tools",
|
|
32
|
-
"software development",
|
|
33
|
-
"private network integration"
|
|
29
|
+
"TypeScript",
|
|
30
|
+
"Rust",
|
|
31
|
+
"SmartProxy",
|
|
32
|
+
"DcRouter",
|
|
33
|
+
"flow control"
|
|
34
34
|
]
|
|
35
35
|
},
|
|
36
36
|
"release": {
|
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/remoteingress',
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.15.0',
|
|
7
7
|
description: 'Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLCtKQUErSjtDQUM3SyxDQUFBIn0=
|
|
@@ -15,7 +15,17 @@ export declare class RemoteIngressEdge extends EventEmitter {
|
|
|
15
15
|
private restartBackoffMs;
|
|
16
16
|
private restartAttempts;
|
|
17
17
|
private statusInterval;
|
|
18
|
+
private nft;
|
|
18
19
|
constructor();
|
|
20
|
+
/**
|
|
21
|
+
* Initialize the nftables manager. Fails gracefully if not running as root.
|
|
22
|
+
*/
|
|
23
|
+
private initNft;
|
|
24
|
+
/**
|
|
25
|
+
* Apply firewall configuration received from the hub.
|
|
26
|
+
* Performs a full replacement: cleans up existing rules, then applies the new config.
|
|
27
|
+
*/
|
|
28
|
+
private applyFirewallConfig;
|
|
19
29
|
/**
|
|
20
30
|
* Start the edge — spawns the Rust binary and connects to the hub.
|
|
21
31
|
* Accepts either a connection token or an explicit IEdgeConfig.
|
|
@@ -11,6 +11,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
11
11
|
this.savedConfig = null;
|
|
12
12
|
this.restartBackoffMs = 1000;
|
|
13
13
|
this.restartAttempts = 0;
|
|
14
|
+
this.nft = null;
|
|
14
15
|
/**
|
|
15
16
|
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
16
17
|
*/
|
|
@@ -53,6 +54,8 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
53
54
|
this.started = true;
|
|
54
55
|
this.restartAttempts = 0;
|
|
55
56
|
this.restartBackoffMs = 1000;
|
|
57
|
+
// Re-initialize nftables (hub will re-push config via handshake)
|
|
58
|
+
await this.initNft();
|
|
56
59
|
// Restart periodic status logging
|
|
57
60
|
this.statusInterval = setInterval(async () => {
|
|
58
61
|
try {
|
|
@@ -119,6 +122,79 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
119
122
|
console.log(`[RemoteIngressEdge] Ports updated by hub: ${data.listenPorts.join(', ')}`);
|
|
120
123
|
this.emit('portsUpdated', data);
|
|
121
124
|
});
|
|
125
|
+
this.bridge.on('management:firewallConfigUpdated', (data) => {
|
|
126
|
+
console.log(`[RemoteIngressEdge] Firewall config updated from hub`);
|
|
127
|
+
this.applyFirewallConfig(data.firewallConfig);
|
|
128
|
+
this.emit('firewallConfigUpdated', data);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Initialize the nftables manager. Fails gracefully if not running as root.
|
|
133
|
+
*/
|
|
134
|
+
async initNft() {
|
|
135
|
+
try {
|
|
136
|
+
this.nft = new plugins.smartnftables.SmartNftables({
|
|
137
|
+
tableName: 'remoteingress',
|
|
138
|
+
dryRun: false,
|
|
139
|
+
});
|
|
140
|
+
await this.nft.initialize();
|
|
141
|
+
console.log('[RemoteIngressEdge] SmartNftables initialized');
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
console.warn(`[RemoteIngressEdge] Failed to initialize nftables (not root?): ${err}`);
|
|
145
|
+
this.nft = null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Apply firewall configuration received from the hub.
|
|
150
|
+
* Performs a full replacement: cleans up existing rules, then applies the new config.
|
|
151
|
+
*/
|
|
152
|
+
async applyFirewallConfig(config) {
|
|
153
|
+
if (!this.nft) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
// Full cleanup and reinitialize to replace all rules atomically
|
|
158
|
+
await this.nft.cleanup();
|
|
159
|
+
await this.nft.initialize();
|
|
160
|
+
// Apply blocked IPs
|
|
161
|
+
if (config.blockedIps && config.blockedIps.length > 0) {
|
|
162
|
+
for (const ip of config.blockedIps) {
|
|
163
|
+
await this.nft.firewall.blockIP(ip);
|
|
164
|
+
}
|
|
165
|
+
console.log(`[RemoteIngressEdge] Blocked ${config.blockedIps.length} IPs`);
|
|
166
|
+
}
|
|
167
|
+
// Apply rate limits
|
|
168
|
+
if (config.rateLimits && config.rateLimits.length > 0) {
|
|
169
|
+
for (const rl of config.rateLimits) {
|
|
170
|
+
await this.nft.rateLimit.addRateLimit(rl.id, {
|
|
171
|
+
port: rl.port,
|
|
172
|
+
protocol: rl.protocol,
|
|
173
|
+
rate: rl.rate,
|
|
174
|
+
burst: rl.burst,
|
|
175
|
+
perSourceIP: rl.perSourceIP,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
console.log(`[RemoteIngressEdge] Applied ${config.rateLimits.length} rate limits`);
|
|
179
|
+
}
|
|
180
|
+
// Apply firewall rules
|
|
181
|
+
if (config.rules && config.rules.length > 0) {
|
|
182
|
+
for (const rule of config.rules) {
|
|
183
|
+
await this.nft.firewall.addRule(rule.id, {
|
|
184
|
+
direction: rule.direction,
|
|
185
|
+
action: rule.action,
|
|
186
|
+
sourceIP: rule.sourceIP,
|
|
187
|
+
destPort: rule.destPort,
|
|
188
|
+
protocol: rule.protocol,
|
|
189
|
+
comment: rule.comment,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
console.log(`[RemoteIngressEdge] Applied ${config.rules.length} firewall rules`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
console.error(`[RemoteIngressEdge] Failed to apply firewall config: ${err}`);
|
|
197
|
+
}
|
|
122
198
|
}
|
|
123
199
|
/**
|
|
124
200
|
* Start the edge — spawns the Rust binary and connects to the hub.
|
|
@@ -158,6 +234,8 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
158
234
|
this.started = true;
|
|
159
235
|
this.restartAttempts = 0;
|
|
160
236
|
this.restartBackoffMs = 1000;
|
|
237
|
+
// Initialize nftables (graceful degradation if not root)
|
|
238
|
+
await this.initNft();
|
|
161
239
|
// Start periodic status logging
|
|
162
240
|
this.statusInterval = setInterval(async () => {
|
|
163
241
|
try {
|
|
@@ -180,6 +258,16 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
180
258
|
clearInterval(this.statusInterval);
|
|
181
259
|
this.statusInterval = undefined;
|
|
182
260
|
}
|
|
261
|
+
// Clean up nftables rules before stopping
|
|
262
|
+
if (this.nft) {
|
|
263
|
+
try {
|
|
264
|
+
await this.nft.cleanup();
|
|
265
|
+
}
|
|
266
|
+
catch (err) {
|
|
267
|
+
console.warn(`[RemoteIngressEdge] nftables cleanup error: ${err}`);
|
|
268
|
+
}
|
|
269
|
+
this.nft = null;
|
|
270
|
+
}
|
|
183
271
|
if (this.started) {
|
|
184
272
|
try {
|
|
185
273
|
await this.bridge.sendCommand('stopEdge', {});
|
|
@@ -206,4 +294,4 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
206
294
|
return this.bridge.running;
|
|
207
295
|
}
|
|
208
296
|
}
|
|
209
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucmVtb3RlaW5ncmVzc2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQTRDM0QsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLENBQUM7QUFDaEMsTUFBTSxzQkFBc0IsR0FBRyxNQUFNLENBQUM7QUFFdEMsTUFBTSxPQUFPLGlCQUFrQixTQUFRLFlBQVk7SUFTakQ7UUFDRSxLQUFLLEVBQUUsQ0FBQztRQVJGLFlBQU8sR0FBRyxLQUFLLENBQUM7UUFDaEIsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUNqQixnQkFBVyxHQUF1QixJQUFJLENBQUM7UUFDdkMscUJBQWdCLEdBQUcsSUFBSSxDQUFDO1FBQ3hCLG9CQUFlLEdBQUcsQ0FBQyxDQUFDO1FBMEo1Qjs7V0FFRztRQUNLLHdCQUFtQixHQUFHLEtBQUssRUFBRSxJQUFtQixFQUFFLE1BQXFCLEVBQUUsRUFBRTtZQUNqRixJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUN4RCxPQUFPO1lBQ1QsQ0FBQztZQUVELE9BQU8sQ0FBQyxLQUFLLENBQ1gsaURBQWlELElBQUksWUFBWSxNQUFNLEtBQUs7Z0JBQzVFLFdBQVcsSUFBSSxDQUFDLGVBQWUsR0FBRyxDQUFDLElBQUksb0JBQW9CLEVBQUUsQ0FDOUQsQ0FBQztZQUVGLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO1lBRXJCLG1EQUFtRDtZQUNuRCxJQUFJLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztnQkFD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
|
|
297
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucmVtb3RlaW5ncmVzc2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQTZDM0QsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLENBQUM7QUFDaEMsTUFBTSxzQkFBc0IsR0FBRyxNQUFNLENBQUM7QUFFdEMsTUFBTSxPQUFPLGlCQUFrQixTQUFRLFlBQVk7SUFVakQ7UUFDRSxLQUFLLEVBQUUsQ0FBQztRQVRGLFlBQU8sR0FBRyxLQUFLLENBQUM7UUFDaEIsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUNqQixnQkFBVyxHQUF1QixJQUFJLENBQUM7UUFDdkMscUJBQWdCLEdBQUcsSUFBSSxDQUFDO1FBQ3hCLG9CQUFlLEdBQUcsQ0FBQyxDQUFDO1FBRXBCLFFBQUcsR0FBb0UsSUFBSSxDQUFDO1FBa1BwRjs7V0FFRztRQUNLLHdCQUFtQixHQUFHLEtBQUssRUFBRSxJQUFtQixFQUFFLE1BQXFCLEVBQUUsRUFBRTtZQUNqRixJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUN4RCxPQUFPO1lBQ1QsQ0FBQztZQUVELE9BQU8sQ0FBQyxLQUFLLENBQ1gsaURBQWlELElBQUksWUFBWSxNQUFNLEtBQUs7Z0JBQzVFLFdBQVcsSUFBSSxDQUFDLGVBQWUsR0FBRyxDQUFDLElBQUksb0JBQW9CLEVBQUUsQ0FDOUQsQ0FBQztZQUVGLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO1lBRXJCLG1EQUFtRDtZQUNuRCxJQUFJLElB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
|
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
+
export interface IFirewallRateLimit {
|
|
3
|
+
id: string;
|
|
4
|
+
port: number;
|
|
5
|
+
protocol?: 'tcp' | 'udp';
|
|
6
|
+
rate: string;
|
|
7
|
+
burst?: number;
|
|
8
|
+
perSourceIP?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface IFirewallRule {
|
|
11
|
+
id: string;
|
|
12
|
+
direction: 'input' | 'output' | 'forward';
|
|
13
|
+
action: 'accept' | 'drop' | 'reject';
|
|
14
|
+
sourceIP?: string;
|
|
15
|
+
destPort?: number;
|
|
16
|
+
protocol?: 'tcp' | 'udp';
|
|
17
|
+
comment?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface IFirewallConfig {
|
|
20
|
+
blockedIps?: string[];
|
|
21
|
+
rateLimits?: IFirewallRateLimit[];
|
|
22
|
+
rules?: IFirewallRule[];
|
|
23
|
+
}
|
|
2
24
|
export interface IHubConfig {
|
|
3
25
|
tunnelPort?: number;
|
|
4
26
|
targetHost?: string;
|
|
@@ -13,6 +35,7 @@ type TAllowedEdge = {
|
|
|
13
35
|
listenPorts?: number[];
|
|
14
36
|
listenPortsUdp?: number[];
|
|
15
37
|
stunIntervalSecs?: number;
|
|
38
|
+
firewallConfig?: IFirewallConfig;
|
|
16
39
|
};
|
|
17
40
|
export declare class RemoteIngressHub extends EventEmitter {
|
|
18
41
|
private bridge;
|
|
@@ -165,4 +165,4 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
165
165
|
return this.bridge.running;
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
168
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxRQUFRLENBQUM7QUE4RXRDLE1BQU0sb0JBQW9CLEdBQUcsRUFBRSxDQUFDO0FBQ2hDLE1BQU0sc0JBQXNCLEdBQUcsTUFBTSxDQUFDO0FBRXRDLE1BQU0sT0FBTyxnQkFBaUIsU0FBUSxZQUFZO0lBU2hEO1FBQ0UsS0FBSyxFQUFFLENBQUM7UUFSRixZQUFPLEdBQUcsS0FBSyxDQUFDO1FBQ2hCLGFBQVEsR0FBRyxLQUFLLENBQUM7UUFDakIsZ0JBQVcsR0FBc0IsSUFBSSxDQUFDO1FBQ3RDLGVBQVUsR0FBbUIsRUFBRSxDQUFDO1FBQ2hDLHFCQUFnQixHQUFHLElBQUksQ0FBQztRQUN4QixvQkFBZSxHQUFHLENBQUMsQ0FBQztRQTJINUI7O1dBRUc7UUFDSyx3QkFBbUIsR0FBRyxLQUFLLEVBQUUsSUFBbUIsRUFBRSxNQUFxQixFQUFFLEVBQUU7WUFDakYsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDeEQsT0FBTztZQUNULENBQUM7WUFFRCxPQUFPLENBQUMsS0FBSyxDQUNYLGdEQUFnRCxJQUFJLFlBQVksTUFBTSxLQUFLO2dCQUMzRSxXQUFXLElBQUksQ0FBQyxlQUFlLEdBQUcsQ0FBQyxJQUFJLG9CQUFvQixFQUFFLENBQzlELENBQUM7WUFFRixJQUFJLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztZQUVyQixJQUFJLElBQUksQ0FBQyxlQUFlLElBQUksb0JBQW9CLEVBQUUsQ0FBQztnQkFDakQsT0FBTyxDQUFDLEtBQUssQ0FB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
|
package/dist_ts/plugins.d.ts
CHANGED
package/dist_ts/plugins.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
export { path };
|
|
4
4
|
// @push.rocks scope
|
|
5
|
+
import * as smartnftables from '@push.rocks/smartnftables';
|
|
5
6
|
import * as smartrust from '@push.rocks/smartrust';
|
|
6
|
-
export { smartrust };
|
|
7
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
7
|
+
export { smartnftables, smartrust };
|
|
8
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sRUFBRSxJQUFJLEVBQUUsQ0FBQztBQUVoQixvQkFBb0I7QUFDcEIsT0FBTyxLQUFLLGFBQWEsTUFBTSwyQkFBMkIsQ0FBQztBQUMzRCxPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBQ25ELE9BQU8sRUFBRSxhQUFhLEVBQUUsU0FBUyxFQUFFLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/remoteingress",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.15.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@push.rocks/qenv": "^6.1.3",
|
|
27
|
+
"@push.rocks/smartnftables": "^1.0.1",
|
|
27
28
|
"@push.rocks/smartrust": "^1.3.2"
|
|
28
29
|
},
|
|
29
30
|
"repository": {
|
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @serve.zone/remoteingress
|
|
2
2
|
|
|
3
|
-
Edge ingress tunnel for DcRouter — tunnels **TCP and UDP** traffic from the network edge to a private DcRouter/SmartProxy cluster over encrypted TLS or QUIC connections, preserving the original client IP via PROXY protocol.
|
|
3
|
+
Edge ingress tunnel for DcRouter — tunnels **TCP and UDP** traffic from the network edge to a private DcRouter/SmartProxy cluster over encrypted TLS or QUIC connections, preserving the original client IP via PROXY protocol. Includes **hub-controlled nftables firewall** for IP blocking, rate limiting, and custom firewall rules applied directly at the edge.
|
|
4
4
|
|
|
5
5
|
## Issue Reporting and Security
|
|
6
6
|
|
|
@@ -12,49 +12,55 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
12
12
|
pnpm install @serve.zone/remoteingress
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Architecture
|
|
16
16
|
|
|
17
17
|
`@serve.zone/remoteingress` uses a **Hub/Edge** topology with a high-performance Rust core and a TypeScript API surface:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
│
|
|
23
|
-
│
|
|
24
|
-
│
|
|
25
|
-
│
|
|
26
|
-
│
|
|
27
|
-
│
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
20
|
+
TLS or QUIC Tunnel
|
|
21
|
+
┌─────────────────────┐ ◄══════════════════════════► ┌─────────────────────┐
|
|
22
|
+
│ Network Edge │ TCP+TLS: frame mux │ Private Cluster │
|
|
23
|
+
│ │ QUIC: native streams │ │
|
|
24
|
+
│ RemoteIngressEdge │ UDP: QUIC datagrams │ RemoteIngressHub │
|
|
25
|
+
│ │ │ │
|
|
26
|
+
│ Accepts TCP & UDP │ │ Forwards to │
|
|
27
|
+
│ on hub-assigned │ │ SmartProxy on │
|
|
28
|
+
│ ports │ │ local ports │
|
|
29
|
+
│ │ │ │
|
|
30
|
+
│ 🔥 nftables rules │ ◄── firewall config pushed ── │ Configures edge │
|
|
31
|
+
│ applied locally │ via FRAME_CONFIG │ firewalls remotely │
|
|
32
|
+
└─────────────────────┘ └─────────────────────┘
|
|
33
|
+
▲ │
|
|
34
|
+
│ TCP + UDP from end users ▼
|
|
35
|
+
Internet DcRouter / SmartProxy
|
|
32
36
|
```
|
|
33
37
|
|
|
34
38
|
| Component | Role |
|
|
35
39
|
|-----------|------|
|
|
36
|
-
| **RemoteIngressEdge** | Deployed at the network edge (VPS, cloud instance). Listens on TCP and UDP ports assigned by the hub, accepts connections/datagrams, and tunnels them to the hub. Ports are hot-reloadable at runtime. |
|
|
37
|
-
| **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams/datagrams, and forwards each to SmartProxy with PROXY protocol headers so the real client IP is preserved. |
|
|
40
|
+
| **RemoteIngressEdge** | Deployed at the network edge (VPS, cloud instance). Runs as root. Listens on TCP and UDP ports assigned by the hub, accepts connections/datagrams, and tunnels them to the hub. Applies nftables firewall rules pushed by the hub for IP blocking and rate limiting. Ports and firewall config are hot-reloadable at runtime. |
|
|
41
|
+
| **RemoteIngressHub** | Deployed alongside DcRouter/SmartProxy in a private cluster. Accepts edge connections, demuxes streams/datagrams, and forwards each to SmartProxy with PROXY protocol headers so the real client IP is preserved. Pushes firewall configuration to edges. |
|
|
38
42
|
| **Rust Binary** (`remoteingress-bin`) | The performance-critical networking core. Managed via `@push.rocks/smartrust` RustBridge IPC — you never interact with it directly. Cross-compiled for `linux/amd64` and `linux/arm64`. |
|
|
39
43
|
|
|
40
|
-
###
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
### ⚡ Key Features
|
|
45
|
+
|
|
46
|
+
- **Dual transport** — choose between TCP+TLS (frame-multiplexed) or QUIC (native stream multiplexing, zero head-of-line blocking)
|
|
47
|
+
- **TCP + UDP tunneling** — tunnel any TCP connection or UDP datagram through the same edge/hub pair
|
|
48
|
+
- **PROXY protocol v1 & v2** — SmartProxy sees the real client IP for both TCP (v1 text) and UDP (v2 binary)
|
|
49
|
+
- **Hub-controlled firewall** — push nftables rules (IP blocking, rate limiting, custom rules) from the hub to edges via `@push.rocks/smartnftables`
|
|
50
|
+
- **Multiplexed streams** — thousands of concurrent TCP connections over a single tunnel
|
|
51
|
+
- **QUIC datagrams** — UDP traffic forwarded via QUIC unreliable datagrams for lowest possible latency
|
|
52
|
+
- **Shared-secret authentication** — edges must present valid credentials to connect
|
|
53
|
+
- **Connection tokens** — encode all connection details into a single opaque base64url string
|
|
54
|
+
- **STUN-based public IP discovery** — edges automatically discover their public IP via Cloudflare STUN
|
|
55
|
+
- **Auto-reconnect** with exponential backoff if the tunnel drops
|
|
56
|
+
- **Dynamic port configuration** — the hub assigns TCP and UDP listen ports per edge, hot-reloadable at runtime
|
|
57
|
+
- **Event-driven** — both Hub and Edge extend `EventEmitter` for real-time monitoring
|
|
58
|
+
- **3-tier QoS** — control frames, normal data, and sustained (elephant flow) traffic each get their own priority queue
|
|
59
|
+
- **Adaptive flow control** — per-stream windows scale with active stream count to prevent memory overuse
|
|
60
|
+
- **UDP session management** — automatic session tracking with 60s idle timeout and cleanup
|
|
61
|
+
- **Crash recovery** — automatic restart with exponential backoff if the Rust binary crashes unexpectedly
|
|
62
|
+
|
|
63
|
+
## Usage
|
|
58
64
|
|
|
59
65
|
Both classes are imported from the package and communicate with the Rust binary under the hood.
|
|
60
66
|
|
|
@@ -77,7 +83,7 @@ await hub.start({
|
|
|
77
83
|
targetHost: '127.0.0.1', // SmartProxy host to forward traffic to
|
|
78
84
|
});
|
|
79
85
|
|
|
80
|
-
// Register allowed edges with TCP and UDP listen ports
|
|
86
|
+
// Register allowed edges with TCP and UDP listen ports + firewall config
|
|
81
87
|
await hub.updateAllowedEdges([
|
|
82
88
|
{
|
|
83
89
|
id: 'edge-nyc-01',
|
|
@@ -85,6 +91,15 @@ await hub.updateAllowedEdges([
|
|
|
85
91
|
listenPorts: [80, 443], // TCP ports the edge should listen on
|
|
86
92
|
listenPortsUdp: [53, 51820], // UDP ports (e.g., DNS, WireGuard)
|
|
87
93
|
stunIntervalSecs: 300,
|
|
94
|
+
firewallConfig: {
|
|
95
|
+
blockedIps: ['192.168.1.100', '10.0.0.0/8'],
|
|
96
|
+
rateLimits: [
|
|
97
|
+
{ id: 'http-rate', port: 80, protocol: 'tcp', rate: '100/second', perSourceIP: true },
|
|
98
|
+
],
|
|
99
|
+
rules: [
|
|
100
|
+
{ id: 'allow-ssh', direction: 'input', action: 'accept', sourceIP: '10.0.0.0/24', destPort: 22, protocol: 'tcp' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
88
103
|
},
|
|
89
104
|
{
|
|
90
105
|
id: 'edge-fra-02',
|
|
@@ -93,13 +108,19 @@ await hub.updateAllowedEdges([
|
|
|
93
108
|
},
|
|
94
109
|
]);
|
|
95
110
|
|
|
96
|
-
// Dynamically update ports — changes are pushed instantly to connected edges
|
|
111
|
+
// Dynamically update ports and firewall — changes are pushed instantly to connected edges
|
|
97
112
|
await hub.updateAllowedEdges([
|
|
98
113
|
{
|
|
99
114
|
id: 'edge-nyc-01',
|
|
100
115
|
secret: 'supersecrettoken1',
|
|
101
116
|
listenPorts: [80, 443, 8443], // added TCP port 8443
|
|
102
117
|
listenPortsUdp: [53], // removed WireGuard UDP port
|
|
118
|
+
firewallConfig: {
|
|
119
|
+
blockedIps: ['192.168.1.100', '10.0.0.0/8', '203.0.113.50'], // added new blocked IP
|
|
120
|
+
rateLimits: [
|
|
121
|
+
{ id: 'http-rate', port: 80, protocol: 'tcp', rate: '200/second', perSourceIP: true },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
103
124
|
},
|
|
104
125
|
]);
|
|
105
126
|
|
|
@@ -112,7 +133,7 @@ await hub.stop();
|
|
|
112
133
|
|
|
113
134
|
### Setting Up the Edge (Network Edge Side)
|
|
114
135
|
|
|
115
|
-
The edge can connect via **TCP+TLS** (default) or **QUIC** transport.
|
|
136
|
+
The edge can connect via **TCP+TLS** (default) or **QUIC** transport. Edges run as **root** so they can bind to privileged ports and apply nftables firewall rules.
|
|
116
137
|
|
|
117
138
|
#### Option A: Connection Token (Recommended)
|
|
118
139
|
|
|
@@ -125,6 +146,7 @@ edge.on('tunnelConnected', () => console.log('Tunnel established'));
|
|
|
125
146
|
edge.on('tunnelDisconnected', () => console.log('Tunnel lost — will auto-reconnect'));
|
|
126
147
|
edge.on('publicIpDiscovered', ({ ip }) => console.log(`Public IP: ${ip}`));
|
|
127
148
|
edge.on('portsAssigned', ({ listenPorts }) => console.log(`TCP ports: ${listenPorts}`));
|
|
149
|
+
edge.on('firewallConfigUpdated', () => console.log('Firewall rules applied'));
|
|
128
150
|
|
|
129
151
|
await edge.start({
|
|
130
152
|
token: 'eyJoIjoiaHViLmV4YW1wbGUuY29tIiwi...',
|
|
@@ -160,7 +182,7 @@ await edge.stop();
|
|
|
160
182
|
| `'quic'` | QUIC with native stream multiplexing. Eliminates head-of-line blocking. Uses QUIC datagrams for UDP traffic. |
|
|
161
183
|
| `'quicWithFallback'` | Tries QUIC first (5s timeout), falls back to TCP+TLS if UDP is blocked by the network. |
|
|
162
184
|
|
|
163
|
-
###
|
|
185
|
+
### Connection Tokens
|
|
164
186
|
|
|
165
187
|
Encode all connection details into a single opaque string for easy distribution:
|
|
166
188
|
|
|
@@ -183,7 +205,78 @@ const data = decodeConnectionToken(token);
|
|
|
183
205
|
|
|
184
206
|
Tokens are base64url-encoded — safe for environment variables, CLI arguments, and config files.
|
|
185
207
|
|
|
186
|
-
##
|
|
208
|
+
## 🔥 Hub-Controlled Firewall
|
|
209
|
+
|
|
210
|
+
Edges run as root and use `@push.rocks/smartnftables` to apply nftables rules pushed from the hub. This gives you centralized control over network-level security at every edge node.
|
|
211
|
+
|
|
212
|
+
### How It Works
|
|
213
|
+
|
|
214
|
+
1. The hub includes `firewallConfig` when calling `updateAllowedEdges()`
|
|
215
|
+
2. The config flows through the Rust binary as an opaque JSON blob via `FRAME_CONFIG`
|
|
216
|
+
3. The edge TypeScript layer receives it and applies the rules using `SmartNftables`
|
|
217
|
+
4. On each config update, all previous rules are replaced atomically (full replacement, not incremental)
|
|
218
|
+
|
|
219
|
+
### Firewall Config Structure
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
interface IFirewallConfig {
|
|
223
|
+
blockedIps?: string[]; // IPs or CIDRs to block (e.g., '1.2.3.4', '10.0.0.0/8')
|
|
224
|
+
rateLimits?: IFirewallRateLimit[];
|
|
225
|
+
rules?: IFirewallRule[];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
interface IFirewallRateLimit {
|
|
229
|
+
id: string; // unique identifier for this rate limit
|
|
230
|
+
port: number; // port to rate-limit
|
|
231
|
+
protocol?: 'tcp' | 'udp'; // default: both
|
|
232
|
+
rate: string; // e.g., '100/second', '1000/minute'
|
|
233
|
+
burst?: number; // burst allowance
|
|
234
|
+
perSourceIP?: boolean; // per-client rate limiting (recommended)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
interface IFirewallRule {
|
|
238
|
+
id: string; // unique identifier for this rule
|
|
239
|
+
direction: 'input' | 'output' | 'forward';
|
|
240
|
+
action: 'accept' | 'drop' | 'reject';
|
|
241
|
+
sourceIP?: string; // source IP or CIDR
|
|
242
|
+
destPort?: number; // destination port
|
|
243
|
+
protocol?: 'tcp' | 'udp';
|
|
244
|
+
comment?: string;
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Example: Rate Limiting + IP Blocking
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
await hub.updateAllowedEdges([
|
|
252
|
+
{
|
|
253
|
+
id: 'edge-nyc-01',
|
|
254
|
+
secret: 'secret',
|
|
255
|
+
listenPorts: [80, 443],
|
|
256
|
+
firewallConfig: {
|
|
257
|
+
// Block known bad actors
|
|
258
|
+
blockedIps: ['198.51.100.0/24', '203.0.113.50'],
|
|
259
|
+
|
|
260
|
+
// Rate limit HTTP traffic per source IP
|
|
261
|
+
rateLimits: [
|
|
262
|
+
{ id: 'http', port: 80, protocol: 'tcp', rate: '100/second', burst: 50, perSourceIP: true },
|
|
263
|
+
{ id: 'https', port: 443, protocol: 'tcp', rate: '200/second', burst: 100, perSourceIP: true },
|
|
264
|
+
],
|
|
265
|
+
|
|
266
|
+
// Allow monitoring from trusted subnet
|
|
267
|
+
rules: [
|
|
268
|
+
{ id: 'monitoring', direction: 'input', action: 'accept', sourceIP: '10.0.0.0/24', destPort: 9090, protocol: 'tcp', comment: 'Prometheus scraping' },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
]);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Graceful Degradation
|
|
276
|
+
|
|
277
|
+
If the edge isn't running as root or nftables is unavailable, the SmartNftables initialization logs a warning and continues operating normally — the tunnel works fine, just without kernel-level firewall rules.
|
|
278
|
+
|
|
279
|
+
## API Reference
|
|
187
280
|
|
|
188
281
|
### `RemoteIngressHub`
|
|
189
282
|
|
|
@@ -191,22 +284,22 @@ Tokens are base64url-encoded — safe for environment variables, CLI arguments,
|
|
|
191
284
|
|-------------------|-------------|
|
|
192
285
|
| `start(config?)` | Start the hub. Config: `{ tunnelPort?, targetHost?, tls?: { certPem?, keyPem? } }`. Listens on both TCP and UDP (QUIC) on the tunnel port. |
|
|
193
286
|
| `stop()` | Graceful shutdown. |
|
|
194
|
-
| `updateAllowedEdges(edges)` | Set authorized edges. Each: `{ id, secret, listenPorts?, listenPortsUdp?, stunIntervalSecs? }`. Port changes are pushed to connected edges in real time. |
|
|
287
|
+
| `updateAllowedEdges(edges)` | Set authorized edges. Each: `{ id, secret, listenPorts?, listenPortsUdp?, stunIntervalSecs?, firewallConfig? }`. Port and firewall changes are pushed to connected edges in real time. |
|
|
195
288
|
| `getStatus()` | Returns `{ running, tunnelPort, connectedEdges: [...] }`. |
|
|
196
289
|
| `running` | `boolean` — whether the Rust binary is alive. |
|
|
197
290
|
|
|
198
|
-
**Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed`
|
|
291
|
+
**Events:** `edgeConnected`, `edgeDisconnected`, `streamOpened`, `streamClosed`, `crashRecovered`, `crashRecoveryFailed`
|
|
199
292
|
|
|
200
293
|
### `RemoteIngressEdge`
|
|
201
294
|
|
|
202
295
|
| Method / Property | Description |
|
|
203
296
|
|-------------------|-------------|
|
|
204
|
-
| `start(config)` | Connect to hub. Accepts `{ token }` or `{ hubHost, hubPort, edgeId, secret, transportMode? }`. |
|
|
205
|
-
| `stop()` | Graceful shutdown. |
|
|
297
|
+
| `start(config)` | Connect to hub. Accepts `{ token }` or `{ hubHost, hubPort, edgeId, secret, bindAddress?, transportMode? }`. |
|
|
298
|
+
| `stop()` | Graceful shutdown. Cleans up all nftables rules. |
|
|
206
299
|
| `getStatus()` | Returns `{ running, connected, publicIp, activeStreams, listenPorts }`. |
|
|
207
300
|
| `running` | `boolean` — whether the Rust binary is alive. |
|
|
208
301
|
|
|
209
|
-
**Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`, `portsAssigned`, `portsUpdated`
|
|
302
|
+
**Events:** `tunnelConnected`, `tunnelDisconnected`, `publicIpDiscovered`, `portsAssigned`, `portsUpdated`, `firewallConfigUpdated`, `crashRecovered`, `crashRecoveryFailed`
|
|
210
303
|
|
|
211
304
|
### Token Utilities
|
|
212
305
|
|
|
@@ -244,7 +337,7 @@ interface IConnectionTokenData {
|
|
|
244
337
|
}
|
|
245
338
|
```
|
|
246
339
|
|
|
247
|
-
##
|
|
340
|
+
## Wire Protocol
|
|
248
341
|
|
|
249
342
|
### TCP+TLS Transport (Frame Protocol)
|
|
250
343
|
|
|
@@ -261,7 +354,7 @@ The tunnel uses a custom binary frame protocol over a single TLS connection:
|
|
|
261
354
|
| `CLOSE` | `0x03` | Edge → Hub | Client closed connection |
|
|
262
355
|
| `DATA_BACK` | `0x04` | Hub → Edge | Response data (download) |
|
|
263
356
|
| `CLOSE_BACK` | `0x05` | Hub → Edge | Upstream closed connection |
|
|
264
|
-
| `CONFIG` | `0x06` | Hub → Edge | Runtime config update (JSON
|
|
357
|
+
| `CONFIG` | `0x06` | Hub → Edge | Runtime config update (JSON: ports + firewall config) |
|
|
265
358
|
| `PING` | `0x07` | Hub → Edge | Heartbeat probe (every 15s) |
|
|
266
359
|
| `PONG` | `0x08` | Edge → Hub | Heartbeat response |
|
|
267
360
|
| `WINDOW_UPDATE` | `0x09` | Edge → Hub | Flow control: edge consumed N bytes |
|
|
@@ -284,19 +377,20 @@ When using QUIC, the frame protocol is replaced by native QUIC primitives:
|
|
|
284
377
|
1. Edge opens a TLS or QUIC connection to the hub
|
|
285
378
|
2. Edge sends: `EDGE <edgeId> <secret>\n`
|
|
286
379
|
3. Hub verifies credentials (constant-time comparison) and responds with JSON:
|
|
287
|
-
`{"listenPorts":[...],"listenPortsUdp":[...],"stunIntervalSecs":300}\n`
|
|
380
|
+
`{"listenPorts":[...],"listenPortsUdp":[...],"stunIntervalSecs":300,"firewallConfig":{...}}\n`
|
|
288
381
|
4. Edge starts TCP and UDP listeners on the assigned ports
|
|
289
|
-
5.
|
|
382
|
+
5. Edge applies firewall config via nftables (if present and running as root)
|
|
383
|
+
6. Data flows — TCP frames/QUIC streams for TCP traffic, UDP frames/QUIC datagrams for UDP traffic
|
|
290
384
|
|
|
291
|
-
##
|
|
385
|
+
## QoS & Flow Control
|
|
292
386
|
|
|
293
387
|
### Priority Tiers (TCP+TLS Transport)
|
|
294
388
|
|
|
295
389
|
| Tier | Frames | Behavior |
|
|
296
390
|
|------|--------|----------|
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
|
|
|
391
|
+
| **Control** | PING, PONG, WINDOW_UPDATE, OPEN, CLOSE, CONFIG | Always drained first. Never delayed. |
|
|
392
|
+
| **Data** | DATA/DATA_BACK from normal streams, UDP frames | Drained when control queue is empty. |
|
|
393
|
+
| **Sustained** | DATA/DATA_BACK from elephant flows | Lowest priority with guaranteed **1 MB/s** drain rate. |
|
|
300
394
|
|
|
301
395
|
### Sustained Stream Classification
|
|
302
396
|
|
|
@@ -318,17 +412,17 @@ Each TCP stream has a send window from a shared **200 MB budget**:
|
|
|
318
412
|
|
|
319
413
|
UDP traffic uses no flow control — datagrams are fire-and-forget, matching UDP semantics.
|
|
320
414
|
|
|
321
|
-
##
|
|
415
|
+
## Example Scenarios
|
|
322
416
|
|
|
323
|
-
### 1. Expose a Private Cluster to the Internet
|
|
417
|
+
### 1. 🌐 Expose a Private Cluster to the Internet
|
|
324
418
|
|
|
325
419
|
Deploy an Edge on a public VPS, point DNS to its IP. The Edge tunnels all TCP and UDP traffic to the Hub running inside your private cluster. No public ports needed on the cluster.
|
|
326
420
|
|
|
327
|
-
### 2. Multi-Region Edge Ingress
|
|
421
|
+
### 2. 🗺️ Multi-Region Edge Ingress
|
|
328
422
|
|
|
329
423
|
Run Edges in NYC, Frankfurt, and Tokyo — all connecting to a single Hub. Use GeoDNS to route users to their nearest Edge. PROXY protocol ensures the Hub sees real client IPs regardless of which Edge they entered through.
|
|
330
424
|
|
|
331
|
-
### 3. UDP Forwarding (DNS, Gaming, VoIP)
|
|
425
|
+
### 3. 📡 UDP Forwarding (DNS, Gaming, VoIP)
|
|
332
426
|
|
|
333
427
|
Configure UDP listen ports alongside TCP ports. DNS queries, game server traffic, or VoIP packets are tunneled through the same edge/hub connection and forwarded to SmartProxy with a PROXY v2 binary header preserving the client's real IP.
|
|
334
428
|
|
|
@@ -343,7 +437,7 @@ await hub.updateAllowedEdges([
|
|
|
343
437
|
]);
|
|
344
438
|
```
|
|
345
439
|
|
|
346
|
-
### 4. QUIC Transport for Low-Latency
|
|
440
|
+
### 4. 🚀 QUIC Transport for Low-Latency
|
|
347
441
|
|
|
348
442
|
Use QUIC transport to eliminate head-of-line blocking — a lost packet on one stream doesn't stall others. QUIC also enables 0-RTT reconnection and connection migration.
|
|
349
443
|
|
|
@@ -357,11 +451,13 @@ await edge.start({
|
|
|
357
451
|
});
|
|
358
452
|
```
|
|
359
453
|
|
|
360
|
-
### 5. Token-Based Edge Provisioning
|
|
454
|
+
### 5. 🔑 Token-Based Edge Provisioning
|
|
361
455
|
|
|
362
456
|
Generate connection tokens on the hub side and distribute them to edge operators:
|
|
363
457
|
|
|
364
458
|
```typescript
|
|
459
|
+
import { encodeConnectionToken, RemoteIngressEdge } from '@serve.zone/remoteingress';
|
|
460
|
+
|
|
365
461
|
const token = encodeConnectionToken({
|
|
366
462
|
hubHost: 'hub.prod.example.com',
|
|
367
463
|
hubPort: 8443,
|
|
@@ -374,9 +470,33 @@ const edge = new RemoteIngressEdge();
|
|
|
374
470
|
await edge.start({ token });
|
|
375
471
|
```
|
|
376
472
|
|
|
473
|
+
### 6. 🛡️ Centralized Firewall Management
|
|
474
|
+
|
|
475
|
+
Push firewall rules from the hub to all your edge nodes. Block bad actors, rate-limit abusive traffic, and whitelist trusted subnets — all from a single control plane:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
await hub.updateAllowedEdges([
|
|
479
|
+
{
|
|
480
|
+
id: 'edge-nyc-01',
|
|
481
|
+
secret: 'secret',
|
|
482
|
+
listenPorts: [80, 443],
|
|
483
|
+
firewallConfig: {
|
|
484
|
+
blockedIps: ['198.51.100.0/24'],
|
|
485
|
+
rateLimits: [
|
|
486
|
+
{ id: 'https', port: 443, protocol: 'tcp', rate: '500/second', perSourceIP: true, burst: 100 },
|
|
487
|
+
],
|
|
488
|
+
rules: [
|
|
489
|
+
{ id: 'allow-monitoring', direction: 'input', action: 'accept', sourceIP: '10.0.0.0/8', destPort: 9090, protocol: 'tcp' },
|
|
490
|
+
],
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
]);
|
|
494
|
+
// Firewall rules are applied at the edge via nftables within seconds
|
|
495
|
+
```
|
|
496
|
+
|
|
377
497
|
## License and Legal Information
|
|
378
498
|
|
|
379
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [
|
|
499
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
|
380
500
|
|
|
381
501
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
382
502
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/remoteingress',
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.15.0',
|
|
7
7
|
description: 'Edge ingress tunnel for DcRouter - tunnels TCP and UDP traffic from the network edge to SmartProxy over TLS or QUIC, preserving client IP via PROXY protocol.'
|
|
8
8
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import { decodeConnectionToken } from './classes.token.js';
|
|
4
|
+
import type { IFirewallConfig } from './classes.remoteingresshub.js';
|
|
4
5
|
|
|
5
6
|
// Command map for the edge side of remoteingress-bin
|
|
6
7
|
type TEdgeCommands = {
|
|
@@ -55,6 +56,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
55
56
|
private restartBackoffMs = 1000;
|
|
56
57
|
private restartAttempts = 0;
|
|
57
58
|
private statusInterval: ReturnType<typeof setInterval> | undefined;
|
|
59
|
+
private nft: InstanceType<typeof plugins.smartnftables.SmartNftables> | null = null;
|
|
58
60
|
|
|
59
61
|
constructor() {
|
|
60
62
|
super();
|
|
@@ -110,6 +112,83 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
110
112
|
console.log(`[RemoteIngressEdge] Ports updated by hub: ${data.listenPorts.join(', ')}`);
|
|
111
113
|
this.emit('portsUpdated', data);
|
|
112
114
|
});
|
|
115
|
+
this.bridge.on('management:firewallConfigUpdated', (data: { firewallConfig: IFirewallConfig }) => {
|
|
116
|
+
console.log(`[RemoteIngressEdge] Firewall config updated from hub`);
|
|
117
|
+
this.applyFirewallConfig(data.firewallConfig);
|
|
118
|
+
this.emit('firewallConfigUpdated', data);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Initialize the nftables manager. Fails gracefully if not running as root.
|
|
124
|
+
*/
|
|
125
|
+
private async initNft(): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
this.nft = new plugins.smartnftables.SmartNftables({
|
|
128
|
+
tableName: 'remoteingress',
|
|
129
|
+
dryRun: false,
|
|
130
|
+
});
|
|
131
|
+
await this.nft.initialize();
|
|
132
|
+
console.log('[RemoteIngressEdge] SmartNftables initialized');
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.warn(`[RemoteIngressEdge] Failed to initialize nftables (not root?): ${err}`);
|
|
135
|
+
this.nft = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Apply firewall configuration received from the hub.
|
|
141
|
+
* Performs a full replacement: cleans up existing rules, then applies the new config.
|
|
142
|
+
*/
|
|
143
|
+
private async applyFirewallConfig(config: IFirewallConfig): Promise<void> {
|
|
144
|
+
if (!this.nft) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// Full cleanup and reinitialize to replace all rules atomically
|
|
150
|
+
await this.nft.cleanup();
|
|
151
|
+
await this.nft.initialize();
|
|
152
|
+
|
|
153
|
+
// Apply blocked IPs
|
|
154
|
+
if (config.blockedIps && config.blockedIps.length > 0) {
|
|
155
|
+
for (const ip of config.blockedIps) {
|
|
156
|
+
await this.nft.firewall.blockIP(ip);
|
|
157
|
+
}
|
|
158
|
+
console.log(`[RemoteIngressEdge] Blocked ${config.blockedIps.length} IPs`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Apply rate limits
|
|
162
|
+
if (config.rateLimits && config.rateLimits.length > 0) {
|
|
163
|
+
for (const rl of config.rateLimits) {
|
|
164
|
+
await this.nft.rateLimit.addRateLimit(rl.id, {
|
|
165
|
+
port: rl.port,
|
|
166
|
+
protocol: rl.protocol,
|
|
167
|
+
rate: rl.rate,
|
|
168
|
+
burst: rl.burst,
|
|
169
|
+
perSourceIP: rl.perSourceIP,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
console.log(`[RemoteIngressEdge] Applied ${config.rateLimits.length} rate limits`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Apply firewall rules
|
|
176
|
+
if (config.rules && config.rules.length > 0) {
|
|
177
|
+
for (const rule of config.rules) {
|
|
178
|
+
await this.nft.firewall.addRule(rule.id, {
|
|
179
|
+
direction: rule.direction,
|
|
180
|
+
action: rule.action,
|
|
181
|
+
sourceIP: rule.sourceIP,
|
|
182
|
+
destPort: rule.destPort,
|
|
183
|
+
protocol: rule.protocol,
|
|
184
|
+
comment: rule.comment,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
console.log(`[RemoteIngressEdge] Applied ${config.rules.length} firewall rules`);
|
|
188
|
+
}
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error(`[RemoteIngressEdge] Failed to apply firewall config: ${err}`);
|
|
191
|
+
}
|
|
113
192
|
}
|
|
114
193
|
|
|
115
194
|
/**
|
|
@@ -156,6 +235,9 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
156
235
|
this.restartAttempts = 0;
|
|
157
236
|
this.restartBackoffMs = 1000;
|
|
158
237
|
|
|
238
|
+
// Initialize nftables (graceful degradation if not root)
|
|
239
|
+
await this.initNft();
|
|
240
|
+
|
|
159
241
|
// Start periodic status logging
|
|
160
242
|
this.statusInterval = setInterval(async () => {
|
|
161
243
|
try {
|
|
@@ -180,6 +262,15 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
180
262
|
clearInterval(this.statusInterval);
|
|
181
263
|
this.statusInterval = undefined;
|
|
182
264
|
}
|
|
265
|
+
// Clean up nftables rules before stopping
|
|
266
|
+
if (this.nft) {
|
|
267
|
+
try {
|
|
268
|
+
await this.nft.cleanup();
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.warn(`[RemoteIngressEdge] nftables cleanup error: ${err}`);
|
|
271
|
+
}
|
|
272
|
+
this.nft = null;
|
|
273
|
+
}
|
|
183
274
|
if (this.started) {
|
|
184
275
|
try {
|
|
185
276
|
await this.bridge.sendCommand('stopEdge', {} as Record<string, never>);
|
|
@@ -261,6 +352,9 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
261
352
|
this.restartAttempts = 0;
|
|
262
353
|
this.restartBackoffMs = 1000;
|
|
263
354
|
|
|
355
|
+
// Re-initialize nftables (hub will re-push config via handshake)
|
|
356
|
+
await this.initNft();
|
|
357
|
+
|
|
264
358
|
// Restart periodic status logging
|
|
265
359
|
this.statusInterval = setInterval(async () => {
|
|
266
360
|
try {
|
|
@@ -22,7 +22,7 @@ type THubCommands = {
|
|
|
22
22
|
};
|
|
23
23
|
updateAllowedEdges: {
|
|
24
24
|
params: {
|
|
25
|
-
edges: Array<{ id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number }>;
|
|
25
|
+
edges: Array<{ id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig }>;
|
|
26
26
|
};
|
|
27
27
|
result: { updated: boolean };
|
|
28
28
|
};
|
|
@@ -41,6 +41,31 @@ type THubCommands = {
|
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
export interface IFirewallRateLimit {
|
|
45
|
+
id: string;
|
|
46
|
+
port: number;
|
|
47
|
+
protocol?: 'tcp' | 'udp';
|
|
48
|
+
rate: string;
|
|
49
|
+
burst?: number;
|
|
50
|
+
perSourceIP?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface IFirewallRule {
|
|
54
|
+
id: string;
|
|
55
|
+
direction: 'input' | 'output' | 'forward';
|
|
56
|
+
action: 'accept' | 'drop' | 'reject';
|
|
57
|
+
sourceIP?: string;
|
|
58
|
+
destPort?: number;
|
|
59
|
+
protocol?: 'tcp' | 'udp';
|
|
60
|
+
comment?: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface IFirewallConfig {
|
|
64
|
+
blockedIps?: string[];
|
|
65
|
+
rateLimits?: IFirewallRateLimit[];
|
|
66
|
+
rules?: IFirewallRule[];
|
|
67
|
+
}
|
|
68
|
+
|
|
44
69
|
export interface IHubConfig {
|
|
45
70
|
tunnelPort?: number;
|
|
46
71
|
targetHost?: string;
|
|
@@ -50,7 +75,7 @@ export interface IHubConfig {
|
|
|
50
75
|
};
|
|
51
76
|
}
|
|
52
77
|
|
|
53
|
-
type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number };
|
|
78
|
+
type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; listenPortsUdp?: number[]; stunIntervalSecs?: number; firewallConfig?: IFirewallConfig };
|
|
54
79
|
|
|
55
80
|
const MAX_RESTART_ATTEMPTS = 10;
|
|
56
81
|
const MAX_RESTART_BACKOFF_MS = 30_000;
|
package/ts/plugins.ts
CHANGED