@push.rocks/smartproxy 21.1.6 → 22.4.2
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/changelog.md +89 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
- package/dist_ts/core/utils/shared-security-manager.js +66 -1
- package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
- package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
- package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
- package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
- package/dist_ts/proxies/http-proxy/index.js +6 -2
- package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
- package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
- package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
- package/dist_ts/proxies/nftables-proxy/index.js +2 -1
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
- package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
- package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
- package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +0 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
- package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +1 -2
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
- package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
- package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validator.js +266 -6
- package/npmextra.json +12 -6
- package/package.json +34 -24
- package/readme.hints.md +184 -1
- package/readme.md +235 -172
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/utils/shared-security-manager.ts +98 -13
- package/ts/proxies/http-proxy/default-certificates.ts +150 -0
- package/ts/proxies/http-proxy/http-proxy.ts +9 -15
- package/ts/proxies/http-proxy/index.ts +6 -1
- package/ts/proxies/http-proxy/security-manager.ts +141 -161
- package/ts/proxies/nftables-proxy/index.ts +1 -0
- package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
- package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
- package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
- package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
- package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
- package/ts/proxies/smart-proxy/certificate-manager.ts +3 -2
- package/ts/proxies/smart-proxy/connection-manager.ts +21 -8
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +39 -13
- package/ts/proxies/smart-proxy/models/interfaces.ts +0 -1
- package/ts/proxies/smart-proxy/route-connection-handler.ts +88 -16
- package/ts/proxies/smart-proxy/security-manager.ts +98 -86
- package/ts/proxies/smart-proxy/smart-proxy.ts +0 -2
- package/ts/proxies/smart-proxy/tls-manager.ts +1 -37
- package/ts/proxies/smart-proxy/utils/index.ts +3 -5
- package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
- package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
- package/ts/proxies/smart-proxy/utils/route-validator.ts +289 -7
- package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
- package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
|
@@ -3,10 +3,8 @@ import { promisify } from 'util';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as os from 'os';
|
|
6
|
-
import { delay } from '../../core/utils/async-utils.js';
|
|
7
6
|
import { AsyncFileSystem } from '../../core/utils/fs-utils.js';
|
|
8
7
|
import {
|
|
9
|
-
NftBaseError,
|
|
10
8
|
NftValidationError,
|
|
11
9
|
NftExecutionError,
|
|
12
10
|
NftResourceError
|
|
@@ -16,6 +14,12 @@ import type {
|
|
|
16
14
|
NfTableProxyOptions,
|
|
17
15
|
NfTablesStatus
|
|
18
16
|
} from './models/index.js';
|
|
17
|
+
import {
|
|
18
|
+
NftCommandExecutor,
|
|
19
|
+
normalizePortSpec,
|
|
20
|
+
validateSettings,
|
|
21
|
+
filterIPsByFamily
|
|
22
|
+
} from './utils/index.js';
|
|
19
23
|
|
|
20
24
|
const execAsync = promisify(exec);
|
|
21
25
|
|
|
@@ -44,11 +48,12 @@ export class NfTablesProxy {
|
|
|
44
48
|
private ruleTag: string;
|
|
45
49
|
private tableName: string;
|
|
46
50
|
private tempFilePath: string;
|
|
51
|
+
private executor: NftCommandExecutor;
|
|
47
52
|
private static NFT_CMD = 'nft';
|
|
48
53
|
|
|
49
54
|
constructor(settings: NfTableProxyOptions) {
|
|
50
55
|
// Validate inputs to prevent command injection
|
|
51
|
-
|
|
56
|
+
validateSettings(settings);
|
|
52
57
|
|
|
53
58
|
// Set default settings
|
|
54
59
|
this.settings = {
|
|
@@ -74,225 +79,57 @@ export class NfTablesProxy {
|
|
|
74
79
|
// Create a temp file path for batch operations
|
|
75
80
|
this.tempFilePath = path.join(os.tmpdir(), `nft-rules-${Date.now()}.nft`);
|
|
76
81
|
|
|
82
|
+
// Create the command executor
|
|
83
|
+
this.executor = new NftCommandExecutor(
|
|
84
|
+
(level, message, data) => this.log(level, message, data),
|
|
85
|
+
{
|
|
86
|
+
maxRetries: this.settings.maxRetries,
|
|
87
|
+
retryDelayMs: this.settings.retryDelayMs,
|
|
88
|
+
tempFilePath: this.tempFilePath
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
77
92
|
// Register cleanup handlers if deleteOnExit is true
|
|
78
93
|
if (this.settings.deleteOnExit) {
|
|
79
|
-
|
|
94
|
+
// Synchronous cleanup for 'exit' event (only sync code runs here)
|
|
95
|
+
const syncCleanup = () => {
|
|
80
96
|
try {
|
|
81
97
|
this.stopSync();
|
|
82
98
|
} catch (err) {
|
|
83
99
|
this.log('error', 'Error cleaning nftables rules on exit:', { error: err.message });
|
|
84
100
|
}
|
|
85
101
|
};
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
|
|
103
|
+
// Async cleanup for signal handlers (preferred, non-blocking)
|
|
104
|
+
const asyncCleanup = async () => {
|
|
105
|
+
try {
|
|
106
|
+
await this.stop();
|
|
107
|
+
} catch (err) {
|
|
108
|
+
this.log('error', 'Error cleaning nftables rules on signal:', { error: err.message });
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
process.on('exit', syncCleanup);
|
|
88
113
|
process.on('SIGINT', () => {
|
|
89
|
-
|
|
90
|
-
process.exit();
|
|
114
|
+
asyncCleanup().finally(() => process.exit());
|
|
91
115
|
});
|
|
92
116
|
process.on('SIGTERM', () => {
|
|
93
|
-
|
|
94
|
-
process.exit();
|
|
117
|
+
asyncCleanup().finally(() => process.exit());
|
|
95
118
|
});
|
|
96
119
|
}
|
|
97
120
|
}
|
|
98
121
|
|
|
99
|
-
/**
|
|
100
|
-
* Validates settings to prevent command injection and ensure valid values
|
|
101
|
-
*/
|
|
102
|
-
private validateSettings(settings: NfTableProxyOptions): void {
|
|
103
|
-
// Validate port numbers
|
|
104
|
-
const validatePorts = (port: number | PortRange | Array<number | PortRange>) => {
|
|
105
|
-
if (Array.isArray(port)) {
|
|
106
|
-
port.forEach(p => validatePorts(p));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (typeof port === 'number') {
|
|
111
|
-
if (port < 1 || port > 65535) {
|
|
112
|
-
throw new NftValidationError(`Invalid port number: ${port}`);
|
|
113
|
-
}
|
|
114
|
-
} else if (typeof port === 'object') {
|
|
115
|
-
if (port.from < 1 || port.from > 65535 || port.to < 1 || port.to > 65535 || port.from > port.to) {
|
|
116
|
-
throw new NftValidationError(`Invalid port range: ${port.from}-${port.to}`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
validatePorts(settings.fromPort);
|
|
122
|
-
validatePorts(settings.toPort);
|
|
123
|
-
|
|
124
|
-
// Define regex patterns for validation
|
|
125
|
-
const ipRegex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))?$/;
|
|
126
|
-
const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/;
|
|
127
|
-
|
|
128
|
-
// Validate IP addresses
|
|
129
|
-
const validateIPs = (ips?: string[]) => {
|
|
130
|
-
if (!ips) return;
|
|
131
|
-
|
|
132
|
-
for (const ip of ips) {
|
|
133
|
-
if (!ipRegex.test(ip) && !ipv6Regex.test(ip)) {
|
|
134
|
-
throw new NftValidationError(`Invalid IP address format: ${ip}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
validateIPs(settings.ipAllowList);
|
|
140
|
-
validateIPs(settings.ipBlockList);
|
|
141
|
-
|
|
142
|
-
// Validate toHost - only allow hostnames or IPs
|
|
143
|
-
if (settings.toHost) {
|
|
144
|
-
const hostRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
|
|
145
|
-
if (!hostRegex.test(settings.toHost) && !ipRegex.test(settings.toHost) && !ipv6Regex.test(settings.toHost)) {
|
|
146
|
-
throw new NftValidationError(`Invalid host format: ${settings.toHost}`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Validate table name to prevent command injection
|
|
151
|
-
if (settings.tableName) {
|
|
152
|
-
const tableNameRegex = /^[a-zA-Z0-9_]+$/;
|
|
153
|
-
if (!tableNameRegex.test(settings.tableName)) {
|
|
154
|
-
throw new NftValidationError(`Invalid table name: ${settings.tableName}. Only alphanumeric characters and underscores are allowed.`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Validate QoS settings if enabled
|
|
159
|
-
if (settings.qos?.enabled) {
|
|
160
|
-
if (settings.qos.maxRate) {
|
|
161
|
-
const rateRegex = /^[0-9]+[kKmMgG]?bps$/;
|
|
162
|
-
if (!rateRegex.test(settings.qos.maxRate)) {
|
|
163
|
-
throw new NftValidationError(`Invalid rate format: ${settings.qos.maxRate}. Use format like "10mbps", "1gbps", etc.`);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (settings.qos.priority !== undefined) {
|
|
168
|
-
if (settings.qos.priority < 1 || settings.qos.priority > 10 || !Number.isInteger(settings.qos.priority)) {
|
|
169
|
-
throw new NftValidationError(`Invalid priority: ${settings.qos.priority}. Must be an integer between 1 and 10.`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Normalizes port specifications into an array of port ranges
|
|
177
|
-
*/
|
|
178
|
-
private normalizePortSpec(portSpec: number | PortRange | Array<number | PortRange>): PortRange[] {
|
|
179
|
-
const result: PortRange[] = [];
|
|
180
|
-
|
|
181
|
-
if (Array.isArray(portSpec)) {
|
|
182
|
-
// If it's an array, process each element
|
|
183
|
-
for (const spec of portSpec) {
|
|
184
|
-
result.push(...this.normalizePortSpec(spec));
|
|
185
|
-
}
|
|
186
|
-
} else if (typeof portSpec === 'number') {
|
|
187
|
-
// Single port becomes a range with the same start and end
|
|
188
|
-
result.push({ from: portSpec, to: portSpec });
|
|
189
|
-
} else {
|
|
190
|
-
// Already a range
|
|
191
|
-
result.push(portSpec);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Execute a command with retry capability
|
|
199
|
-
*/
|
|
200
|
-
private async executeWithRetry(command: string, maxRetries = 3, retryDelayMs = 1000): Promise<string> {
|
|
201
|
-
let lastError: Error | undefined;
|
|
202
|
-
|
|
203
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
204
|
-
try {
|
|
205
|
-
const { stdout } = await execAsync(command);
|
|
206
|
-
return stdout;
|
|
207
|
-
} catch (err) {
|
|
208
|
-
lastError = err;
|
|
209
|
-
this.log('warn', `Command failed (attempt ${i+1}/${maxRetries}): ${command}`, { error: err.message });
|
|
210
|
-
|
|
211
|
-
// Wait before retry, unless it's the last attempt
|
|
212
|
-
if (i < maxRetries - 1) {
|
|
213
|
-
await delay(retryDelayMs);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Execute system command synchronously with multiple attempts
|
|
223
|
-
* @deprecated This method blocks the event loop and should be avoided. Use executeWithRetry instead.
|
|
224
|
-
* WARNING: This method contains a busy wait loop that will block the entire Node.js event loop!
|
|
225
|
-
*/
|
|
226
|
-
private executeWithRetrySync(command: string, maxRetries = 3, retryDelayMs = 1000): string {
|
|
227
|
-
// Log deprecation warning
|
|
228
|
-
console.warn('[DEPRECATION WARNING] executeWithRetrySync blocks the event loop and should not be used. Consider using the async executeWithRetry method instead.');
|
|
229
|
-
|
|
230
|
-
let lastError: Error | undefined;
|
|
231
|
-
|
|
232
|
-
for (let i = 0; i < maxRetries; i++) {
|
|
233
|
-
try {
|
|
234
|
-
return execSync(command).toString();
|
|
235
|
-
} catch (err) {
|
|
236
|
-
lastError = err;
|
|
237
|
-
this.log('warn', `Command failed (attempt ${i+1}/${maxRetries}): ${command}`, { error: err.message });
|
|
238
|
-
|
|
239
|
-
// Wait before retry, unless it's the last attempt
|
|
240
|
-
if (i < maxRetries - 1) {
|
|
241
|
-
// CRITICAL: This busy wait loop blocks the entire event loop!
|
|
242
|
-
// This is a temporary fallback for sync contexts only.
|
|
243
|
-
// TODO: Remove this method entirely and make all callers async
|
|
244
|
-
const waitUntil = Date.now() + retryDelayMs;
|
|
245
|
-
while (Date.now() < waitUntil) {
|
|
246
|
-
// Busy wait - blocks event loop
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Execute nftables commands with a temporary file
|
|
257
|
-
* This helper handles the common pattern of writing rules to a temp file,
|
|
258
|
-
* executing nftables with the file, and cleaning up
|
|
259
|
-
*/
|
|
260
|
-
private async executeWithTempFile(rulesetContent: string): Promise<void> {
|
|
261
|
-
await AsyncFileSystem.writeFile(this.tempFilePath, rulesetContent);
|
|
262
|
-
|
|
263
|
-
try {
|
|
264
|
-
await this.executeWithRetry(
|
|
265
|
-
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
266
|
-
this.settings.maxRetries,
|
|
267
|
-
this.settings.retryDelayMs
|
|
268
|
-
);
|
|
269
|
-
} finally {
|
|
270
|
-
// Always clean up the temp file
|
|
271
|
-
await AsyncFileSystem.remove(this.tempFilePath);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
122
|
/**
|
|
276
123
|
* Checks if nftables is available and the required modules are loaded
|
|
277
124
|
*/
|
|
278
125
|
private async checkNftablesAvailability(): Promise<boolean> {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
if (this.settings.useAdvancedNAT) {
|
|
284
|
-
try {
|
|
285
|
-
await this.executeWithRetry('lsmod | grep nf_conntrack', this.settings.maxRetries, this.settings.retryDelayMs);
|
|
286
|
-
} catch (err) {
|
|
287
|
-
this.log('warn', 'Connection tracking modules might not be loaded, advanced NAT features may not work');
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return true;
|
|
292
|
-
} catch (err) {
|
|
293
|
-
this.log('error', `nftables is not available: ${err.message}`);
|
|
294
|
-
return false;
|
|
126
|
+
const available = await this.executor.checkAvailability();
|
|
127
|
+
|
|
128
|
+
if (available && this.settings.useAdvancedNAT) {
|
|
129
|
+
await this.executor.checkConntrackModules();
|
|
295
130
|
}
|
|
131
|
+
|
|
132
|
+
return available;
|
|
296
133
|
}
|
|
297
134
|
|
|
298
135
|
/**
|
|
@@ -303,7 +140,7 @@ export class NfTablesProxy {
|
|
|
303
140
|
|
|
304
141
|
try {
|
|
305
142
|
// Check if the table already exists
|
|
306
|
-
const stdout = await this.executeWithRetry(
|
|
143
|
+
const stdout = await this.executor.executeWithRetry(
|
|
307
144
|
`${NfTablesProxy.NFT_CMD} list tables ${family}`,
|
|
308
145
|
this.settings.maxRetries,
|
|
309
146
|
this.settings.retryDelayMs
|
|
@@ -313,7 +150,7 @@ export class NfTablesProxy {
|
|
|
313
150
|
|
|
314
151
|
if (!tableExists) {
|
|
315
152
|
// Create the table
|
|
316
|
-
await this.executeWithRetry(
|
|
153
|
+
await this.executor.executeWithRetry(
|
|
317
154
|
`${NfTablesProxy.NFT_CMD} add table ${family} ${this.tableName}`,
|
|
318
155
|
this.settings.maxRetries,
|
|
319
156
|
this.settings.retryDelayMs
|
|
@@ -322,7 +159,7 @@ export class NfTablesProxy {
|
|
|
322
159
|
this.log('info', `Created table ${family} ${this.tableName}`);
|
|
323
160
|
|
|
324
161
|
// Create the nat chain for the prerouting hook
|
|
325
|
-
await this.executeWithRetry(
|
|
162
|
+
await this.executor.executeWithRetry(
|
|
326
163
|
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_prerouting { type nat hook prerouting priority -100 ; }`,
|
|
327
164
|
this.settings.maxRetries,
|
|
328
165
|
this.settings.retryDelayMs
|
|
@@ -332,7 +169,7 @@ export class NfTablesProxy {
|
|
|
332
169
|
|
|
333
170
|
// Create the nat chain for the postrouting hook if not preserving source IP
|
|
334
171
|
if (!this.settings.preserveSourceIP) {
|
|
335
|
-
await this.executeWithRetry(
|
|
172
|
+
await this.executor.executeWithRetry(
|
|
336
173
|
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_postrouting { type nat hook postrouting priority 100 ; }`,
|
|
337
174
|
this.settings.maxRetries,
|
|
338
175
|
this.settings.retryDelayMs
|
|
@@ -343,7 +180,7 @@ export class NfTablesProxy {
|
|
|
343
180
|
|
|
344
181
|
// Create the chain for NetworkProxy integration if needed
|
|
345
182
|
if (this.settings.netProxyIntegration?.enabled && this.settings.netProxyIntegration.redirectLocalhost) {
|
|
346
|
-
await this.executeWithRetry(
|
|
183
|
+
await this.executor.executeWithRetry(
|
|
347
184
|
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} nat_output { type nat hook output priority 0 ; }`,
|
|
348
185
|
this.settings.maxRetries,
|
|
349
186
|
this.settings.retryDelayMs
|
|
@@ -354,7 +191,7 @@ export class NfTablesProxy {
|
|
|
354
191
|
|
|
355
192
|
// Create the QoS chain if needed
|
|
356
193
|
if (this.settings.qos?.enabled) {
|
|
357
|
-
await this.executeWithRetry(
|
|
194
|
+
await this.executor.executeWithRetry(
|
|
358
195
|
`${NfTablesProxy.NFT_CMD} add chain ${family} ${this.tableName} qos_forward { type filter hook forward priority 0 ; }`,
|
|
359
196
|
this.settings.maxRetries,
|
|
360
197
|
this.settings.retryDelayMs
|
|
@@ -384,11 +221,7 @@ export class NfTablesProxy {
|
|
|
384
221
|
): Promise<boolean> {
|
|
385
222
|
try {
|
|
386
223
|
// Filter IPs based on family
|
|
387
|
-
const filteredIPs = ips
|
|
388
|
-
if (family === 'ip6' && ip.includes(':')) return true;
|
|
389
|
-
if (family === 'ip' && ip.includes('.')) return true;
|
|
390
|
-
return false;
|
|
391
|
-
});
|
|
224
|
+
const filteredIPs = filterIPsByFamily(ips, family as 'ip' | 'ip6');
|
|
392
225
|
|
|
393
226
|
if (filteredIPs.length === 0) {
|
|
394
227
|
this.log('info', `No IP addresses of type ${setType} to add to set ${setName}`);
|
|
@@ -397,7 +230,7 @@ export class NfTablesProxy {
|
|
|
397
230
|
|
|
398
231
|
// Check if set already exists
|
|
399
232
|
try {
|
|
400
|
-
const sets = await this.executeWithRetry(
|
|
233
|
+
const sets = await this.executor.executeWithRetry(
|
|
401
234
|
`${NfTablesProxy.NFT_CMD} list sets ${family} ${this.tableName}`,
|
|
402
235
|
this.settings.maxRetries,
|
|
403
236
|
this.settings.retryDelayMs
|
|
@@ -407,7 +240,7 @@ export class NfTablesProxy {
|
|
|
407
240
|
this.log('info', `IP set ${setName} already exists, will add elements`);
|
|
408
241
|
} else {
|
|
409
242
|
// Create the set
|
|
410
|
-
await this.executeWithRetry(
|
|
243
|
+
await this.executor.executeWithRetry(
|
|
411
244
|
`${NfTablesProxy.NFT_CMD} add set ${family} ${this.tableName} ${setName} { type ${setType}; }`,
|
|
412
245
|
this.settings.maxRetries,
|
|
413
246
|
this.settings.retryDelayMs
|
|
@@ -417,7 +250,7 @@ export class NfTablesProxy {
|
|
|
417
250
|
}
|
|
418
251
|
} catch (err) {
|
|
419
252
|
// Set might not exist yet, create it
|
|
420
|
-
await this.executeWithRetry(
|
|
253
|
+
await this.executor.executeWithRetry(
|
|
421
254
|
`${NfTablesProxy.NFT_CMD} add set ${family} ${this.tableName} ${setName} { type ${setType}; }`,
|
|
422
255
|
this.settings.maxRetries,
|
|
423
256
|
this.settings.retryDelayMs
|
|
@@ -432,7 +265,7 @@ export class NfTablesProxy {
|
|
|
432
265
|
const batch = filteredIPs.slice(i, i + batchSize);
|
|
433
266
|
const elements = batch.join(', ');
|
|
434
267
|
|
|
435
|
-
await this.executeWithRetry(
|
|
268
|
+
await this.executor.executeWithRetry(
|
|
436
269
|
`${NfTablesProxy.NFT_CMD} add element ${family} ${this.tableName} ${setName} { ${elements} }`,
|
|
437
270
|
this.settings.maxRetries,
|
|
438
271
|
this.settings.retryDelayMs
|
|
@@ -575,7 +408,7 @@ export class NfTablesProxy {
|
|
|
575
408
|
// Only write and apply if we have rules to add
|
|
576
409
|
if (rulesetContent) {
|
|
577
410
|
// Apply the ruleset using the helper
|
|
578
|
-
await this.executeWithTempFile(rulesetContent);
|
|
411
|
+
await this.executor.executeWithTempFile(rulesetContent);
|
|
579
412
|
|
|
580
413
|
this.log('info', `Added source IP filter rules for ${family}`);
|
|
581
414
|
|
|
@@ -605,7 +438,7 @@ export class NfTablesProxy {
|
|
|
605
438
|
* Gets a comma-separated list of all ports from a port specification
|
|
606
439
|
*/
|
|
607
440
|
private getAllPorts(portSpec: number | PortRange | Array<number | PortRange>): string {
|
|
608
|
-
const portRanges =
|
|
441
|
+
const portRanges = normalizePortSpec(portSpec);
|
|
609
442
|
const ports: string[] = [];
|
|
610
443
|
|
|
611
444
|
for (const range of portRanges) {
|
|
@@ -632,8 +465,8 @@ export class NfTablesProxy {
|
|
|
632
465
|
|
|
633
466
|
try {
|
|
634
467
|
// Get the port ranges
|
|
635
|
-
const fromPortRanges =
|
|
636
|
-
const toPortRanges =
|
|
468
|
+
const fromPortRanges = normalizePortSpec(this.settings.fromPort);
|
|
469
|
+
const toPortRanges = normalizePortSpec(this.settings.toPort);
|
|
637
470
|
|
|
638
471
|
let rulesetContent = '';
|
|
639
472
|
|
|
@@ -682,7 +515,7 @@ export class NfTablesProxy {
|
|
|
682
515
|
|
|
683
516
|
// Apply the rules if we have any
|
|
684
517
|
if (rulesetContent) {
|
|
685
|
-
await this.executeWithTempFile(rulesetContent);
|
|
518
|
+
await this.executor.executeWithTempFile(rulesetContent);
|
|
686
519
|
|
|
687
520
|
this.log('info', `Added advanced NAT rules for ${family}`);
|
|
688
521
|
|
|
@@ -720,8 +553,8 @@ export class NfTablesProxy {
|
|
|
720
553
|
|
|
721
554
|
try {
|
|
722
555
|
// Normalize port specifications
|
|
723
|
-
const fromPortRanges =
|
|
724
|
-
const toPortRanges =
|
|
556
|
+
const fromPortRanges = normalizePortSpec(this.settings.fromPort);
|
|
557
|
+
const toPortRanges = normalizePortSpec(this.settings.toPort);
|
|
725
558
|
|
|
726
559
|
// Handle the case where fromPort and toPort counts don't match
|
|
727
560
|
if (fromPortRanges.length !== toPortRanges.length) {
|
|
@@ -827,7 +660,7 @@ export class NfTablesProxy {
|
|
|
827
660
|
// Apply the ruleset if we have any rules
|
|
828
661
|
if (rulesetContent) {
|
|
829
662
|
// Apply the ruleset using the helper
|
|
830
|
-
await this.executeWithTempFile(rulesetContent);
|
|
663
|
+
await this.executor.executeWithTempFile(rulesetContent);
|
|
831
664
|
|
|
832
665
|
this.log('info', `Added port forwarding rules for ${family}`);
|
|
833
666
|
|
|
@@ -931,7 +764,7 @@ export class NfTablesProxy {
|
|
|
931
764
|
|
|
932
765
|
// Apply the ruleset if we have any rules
|
|
933
766
|
if (rulesetContent) {
|
|
934
|
-
await this.executeWithTempFile(rulesetContent);
|
|
767
|
+
await this.executor.executeWithTempFile(rulesetContent);
|
|
935
768
|
|
|
936
769
|
this.log('info', `Added port forwarding rules for ${family}`);
|
|
937
770
|
|
|
@@ -984,7 +817,7 @@ export class NfTablesProxy {
|
|
|
984
817
|
// Add priority marking if specified
|
|
985
818
|
if (this.settings.qos.priority !== undefined) {
|
|
986
819
|
// Check if the chain exists
|
|
987
|
-
const chainsOutput = await this.executeWithRetry(
|
|
820
|
+
const chainsOutput = await this.executor.executeWithRetry(
|
|
988
821
|
`${NfTablesProxy.NFT_CMD} list chains ${family} ${this.tableName}`,
|
|
989
822
|
this.settings.maxRetries,
|
|
990
823
|
this.settings.retryDelayMs
|
|
@@ -1000,7 +833,7 @@ export class NfTablesProxy {
|
|
|
1000
833
|
}
|
|
1001
834
|
|
|
1002
835
|
// Add the rules to mark packets with this priority
|
|
1003
|
-
for (const range of
|
|
836
|
+
for (const range of normalizePortSpec(this.settings.toPort)) {
|
|
1004
837
|
const markRule = `add rule ${family} ${this.tableName} ${qosChain} ${this.settings.protocol} dport ${range.from}-${range.to} counter goto prio${this.settings.qos.priority} comment "${this.ruleTag}:QOS_PRIORITY"`;
|
|
1005
838
|
rulesetContent += `${markRule}\n`;
|
|
1006
839
|
|
|
@@ -1017,7 +850,7 @@ export class NfTablesProxy {
|
|
|
1017
850
|
// Apply the ruleset if we have any rules
|
|
1018
851
|
if (rulesetContent) {
|
|
1019
852
|
// Apply the ruleset using the helper
|
|
1020
|
-
await this.executeWithTempFile(rulesetContent);
|
|
853
|
+
await this.executor.executeWithTempFile(rulesetContent);
|
|
1021
854
|
|
|
1022
855
|
this.log('info', `Added QoS rules for ${family}`);
|
|
1023
856
|
|
|
@@ -1060,7 +893,7 @@ export class NfTablesProxy {
|
|
|
1060
893
|
const rule = `add rule ${family} ${this.tableName} ${outputChain} ${this.settings.protocol} daddr ${localhost} redirect to :${netProxyConfig.sslTerminationPort} comment "${this.ruleTag}:NETPROXY_REDIRECT"`;
|
|
1061
894
|
|
|
1062
895
|
// Apply the rule
|
|
1063
|
-
await this.executeWithRetry(
|
|
896
|
+
await this.executor.executeWithRetry(
|
|
1064
897
|
`${NfTablesProxy.NFT_CMD} ${rule}`,
|
|
1065
898
|
this.settings.maxRetries,
|
|
1066
899
|
this.settings.retryDelayMs
|
|
@@ -1103,7 +936,7 @@ export class NfTablesProxy {
|
|
|
1103
936
|
const commentTag = commentMatch[1];
|
|
1104
937
|
|
|
1105
938
|
// List the chain to check if our rule is there
|
|
1106
|
-
const stdout = await this.executeWithRetry(
|
|
939
|
+
const stdout = await this.executor.executeWithRetry(
|
|
1107
940
|
`${NfTablesProxy.NFT_CMD} list chain ${tableFamily} ${tableName} ${chainName}`,
|
|
1108
941
|
this.settings.maxRetries,
|
|
1109
942
|
this.settings.retryDelayMs
|
|
@@ -1139,7 +972,7 @@ export class NfTablesProxy {
|
|
|
1139
972
|
try {
|
|
1140
973
|
// For nftables, create a delete rule by replacing 'add' with 'delete'
|
|
1141
974
|
const deleteRule = rule.ruleContents.replace('add rule', 'delete rule');
|
|
1142
|
-
await this.executeWithRetry(
|
|
975
|
+
await this.executor.executeWithRetry(
|
|
1143
976
|
`${NfTablesProxy.NFT_CMD} ${deleteRule}`,
|
|
1144
977
|
this.settings.maxRetries,
|
|
1145
978
|
this.settings.retryDelayMs
|
|
@@ -1161,7 +994,7 @@ export class NfTablesProxy {
|
|
|
1161
994
|
*/
|
|
1162
995
|
private async tableExists(family: string, tableName: string): Promise<boolean> {
|
|
1163
996
|
try {
|
|
1164
|
-
const stdout = await this.executeWithRetry(
|
|
997
|
+
const stdout = await this.executor.executeWithRetry(
|
|
1165
998
|
`${NfTablesProxy.NFT_CMD} list tables ${family}`,
|
|
1166
999
|
this.settings.maxRetries,
|
|
1167
1000
|
this.settings.retryDelayMs
|
|
@@ -1190,7 +1023,7 @@ export class NfTablesProxy {
|
|
|
1190
1023
|
try {
|
|
1191
1024
|
// Try to get connection metrics if conntrack is available
|
|
1192
1025
|
try {
|
|
1193
|
-
const stdout = await this.executeWithRetry('conntrack -C', this.settings.maxRetries, this.settings.retryDelayMs);
|
|
1026
|
+
const stdout = await this.executor.executeWithRetry('conntrack -C', this.settings.maxRetries, this.settings.retryDelayMs);
|
|
1194
1027
|
metrics.activeConnections = parseInt(stdout.trim(), 10);
|
|
1195
1028
|
} catch (err) {
|
|
1196
1029
|
// conntrack not available, skip this metric
|
|
@@ -1199,7 +1032,7 @@ export class NfTablesProxy {
|
|
|
1199
1032
|
// Try to get forwarded connections count from nftables counters
|
|
1200
1033
|
try {
|
|
1201
1034
|
// Look for counters in our rules
|
|
1202
|
-
const stdout = await this.executeWithRetry(
|
|
1035
|
+
const stdout = await this.executor.executeWithRetry(
|
|
1203
1036
|
`${NfTablesProxy.NFT_CMD} list table ip ${this.tableName}`,
|
|
1204
1037
|
this.settings.maxRetries,
|
|
1205
1038
|
this.settings.retryDelayMs
|
|
@@ -1250,7 +1083,7 @@ export class NfTablesProxy {
|
|
|
1250
1083
|
try {
|
|
1251
1084
|
for (const family of ['ip', 'ip6']) {
|
|
1252
1085
|
try {
|
|
1253
|
-
const stdout = await this.executeWithRetry(
|
|
1086
|
+
const stdout = await this.executor.executeWithRetry(
|
|
1254
1087
|
`${NfTablesProxy.NFT_CMD} list sets ${family} ${this.tableName}`,
|
|
1255
1088
|
this.settings.maxRetries,
|
|
1256
1089
|
this.settings.retryDelayMs
|
|
@@ -1302,7 +1135,7 @@ export class NfTablesProxy {
|
|
|
1302
1135
|
|
|
1303
1136
|
try {
|
|
1304
1137
|
// Get list of configured tables
|
|
1305
|
-
const stdout = await this.executeWithRetry(
|
|
1138
|
+
const stdout = await this.executor.executeWithRetry(
|
|
1306
1139
|
`${NfTablesProxy.NFT_CMD} list tables`,
|
|
1307
1140
|
this.settings.maxRetries,
|
|
1308
1141
|
this.settings.retryDelayMs
|
|
@@ -1408,8 +1241,8 @@ export class NfTablesProxy {
|
|
|
1408
1241
|
// Port forwarding rules
|
|
1409
1242
|
if (this.settings.useAdvancedNAT) {
|
|
1410
1243
|
// Advanced NAT with connection tracking
|
|
1411
|
-
const fromPortRanges =
|
|
1412
|
-
const toPortRanges =
|
|
1244
|
+
const fromPortRanges = normalizePortSpec(this.settings.fromPort);
|
|
1245
|
+
const toPortRanges = normalizePortSpec(this.settings.toPort);
|
|
1413
1246
|
|
|
1414
1247
|
if (fromPortRanges.length === 1 && toPortRanges.length === 1) {
|
|
1415
1248
|
const fromRange = fromPortRanges[0];
|
|
@@ -1425,8 +1258,8 @@ export class NfTablesProxy {
|
|
|
1425
1258
|
}
|
|
1426
1259
|
} else {
|
|
1427
1260
|
// Standard NAT rules
|
|
1428
|
-
const fromRanges =
|
|
1429
|
-
const toRanges =
|
|
1261
|
+
const fromRanges = normalizePortSpec(this.settings.fromPort);
|
|
1262
|
+
const toRanges = normalizePortSpec(this.settings.toPort);
|
|
1430
1263
|
|
|
1431
1264
|
if (fromRanges.length === 1 && toRanges.length === 1) {
|
|
1432
1265
|
const fromRange = fromRanges[0];
|
|
@@ -1472,7 +1305,7 @@ export class NfTablesProxy {
|
|
|
1472
1305
|
if (this.settings.qos.priority !== undefined) {
|
|
1473
1306
|
commands.push(`add chain ip ${this.tableName} prio${this.settings.qos.priority} { type filter hook forward priority ${this.settings.qos.priority * 10}; }`);
|
|
1474
1307
|
|
|
1475
|
-
for (const range of
|
|
1308
|
+
for (const range of normalizePortSpec(this.settings.toPort)) {
|
|
1476
1309
|
commands.push(`add rule ip ${this.tableName} qos_forward ${this.settings.protocol} dport ${range.from}-${range.to} counter goto prio${this.settings.qos.priority} comment "${this.ruleTag}:QOS_PRIORITY"`);
|
|
1477
1310
|
}
|
|
1478
1311
|
}
|
|
@@ -1598,7 +1431,7 @@ export class NfTablesProxy {
|
|
|
1598
1431
|
|
|
1599
1432
|
try {
|
|
1600
1433
|
// Apply the ruleset
|
|
1601
|
-
await this.executeWithRetry(
|
|
1434
|
+
await this.executor.executeWithRetry(
|
|
1602
1435
|
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
1603
1436
|
this.settings.maxRetries,
|
|
1604
1437
|
this.settings.retryDelayMs
|
|
@@ -1623,7 +1456,7 @@ export class NfTablesProxy {
|
|
|
1623
1456
|
const [family, setName] = key.split(':');
|
|
1624
1457
|
|
|
1625
1458
|
try {
|
|
1626
|
-
await this.executeWithRetry(
|
|
1459
|
+
await this.executor.executeWithRetry(
|
|
1627
1460
|
`${NfTablesProxy.NFT_CMD} delete set ${family} ${this.tableName} ${setName}`,
|
|
1628
1461
|
this.settings.maxRetries,
|
|
1629
1462
|
this.settings.retryDelayMs
|
|
@@ -1649,67 +1482,66 @@ export class NfTablesProxy {
|
|
|
1649
1482
|
}
|
|
1650
1483
|
|
|
1651
1484
|
/**
|
|
1652
|
-
* Synchronous version of stop, for use in exit handlers
|
|
1485
|
+
* Synchronous version of stop, for use in exit handlers only.
|
|
1486
|
+
* Uses single-attempt commands without retry (process is exiting anyway).
|
|
1653
1487
|
*/
|
|
1654
1488
|
public stopSync(): void {
|
|
1655
1489
|
try {
|
|
1656
1490
|
let rulesetContent = '';
|
|
1657
|
-
|
|
1491
|
+
|
|
1658
1492
|
// Process rules in reverse order (LIFO)
|
|
1659
1493
|
for (let i = this.rules.length - 1; i >= 0; i--) {
|
|
1660
1494
|
const rule = this.rules[i];
|
|
1661
|
-
|
|
1495
|
+
|
|
1662
1496
|
if (rule.added) {
|
|
1663
1497
|
// Create delete rules by replacing 'add' with 'delete'
|
|
1664
1498
|
const deleteRule = rule.ruleContents.replace('add rule', 'delete rule');
|
|
1665
1499
|
rulesetContent += `${deleteRule}\n`;
|
|
1666
1500
|
}
|
|
1667
1501
|
}
|
|
1668
|
-
|
|
1502
|
+
|
|
1669
1503
|
// Apply the ruleset if we have any rules to delete
|
|
1670
1504
|
if (rulesetContent) {
|
|
1671
1505
|
// Write to temporary file
|
|
1672
1506
|
fs.writeFileSync(this.tempFilePath, rulesetContent);
|
|
1673
|
-
|
|
1674
|
-
// Apply the ruleset
|
|
1675
|
-
this.
|
|
1676
|
-
|
|
1677
|
-
this.settings.maxRetries,
|
|
1678
|
-
this.settings.retryDelayMs
|
|
1679
|
-
);
|
|
1680
|
-
|
|
1507
|
+
|
|
1508
|
+
// Apply the ruleset (single attempt, no retry - process is exiting)
|
|
1509
|
+
this.executor.executeSync(`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`);
|
|
1510
|
+
|
|
1681
1511
|
this.log('info', 'Removed all added rules');
|
|
1682
|
-
|
|
1512
|
+
|
|
1683
1513
|
// Mark all rules as removed
|
|
1684
1514
|
this.rules.forEach(rule => {
|
|
1685
1515
|
rule.added = false;
|
|
1686
1516
|
rule.verified = false;
|
|
1687
1517
|
});
|
|
1688
|
-
|
|
1518
|
+
|
|
1689
1519
|
// Remove temporary file
|
|
1690
|
-
|
|
1520
|
+
try {
|
|
1521
|
+
fs.unlinkSync(this.tempFilePath);
|
|
1522
|
+
} catch {
|
|
1523
|
+
// Ignore - process is exiting
|
|
1524
|
+
}
|
|
1691
1525
|
}
|
|
1692
|
-
|
|
1526
|
+
|
|
1693
1527
|
// Clean up IP sets if we created any
|
|
1694
1528
|
if (this.settings.useIPSets && this.ipSets.size > 0) {
|
|
1695
1529
|
for (const [key, _] of this.ipSets) {
|
|
1696
1530
|
const [family, setName] = key.split(':');
|
|
1697
|
-
|
|
1531
|
+
|
|
1698
1532
|
try {
|
|
1699
|
-
this.
|
|
1700
|
-
`${NfTablesProxy.NFT_CMD} delete set ${family} ${this.tableName} ${setName}
|
|
1701
|
-
this.settings.maxRetries,
|
|
1702
|
-
this.settings.retryDelayMs
|
|
1533
|
+
this.executor.executeSync(
|
|
1534
|
+
`${NfTablesProxy.NFT_CMD} delete set ${family} ${this.tableName} ${setName}`
|
|
1703
1535
|
);
|
|
1704
|
-
} catch
|
|
1536
|
+
} catch {
|
|
1705
1537
|
// Non-critical error, continue
|
|
1706
1538
|
}
|
|
1707
1539
|
}
|
|
1708
1540
|
}
|
|
1709
|
-
|
|
1541
|
+
|
|
1710
1542
|
// Optionally clean up tables if they're empty (sync version)
|
|
1711
1543
|
this.cleanupEmptyTablesSync();
|
|
1712
|
-
|
|
1544
|
+
|
|
1713
1545
|
this.log('info', 'NfTablesProxy stopped successfully');
|
|
1714
1546
|
} catch (err) {
|
|
1715
1547
|
this.log('error', `Error stopping NfTablesProxy: ${err.message}`);
|
|
@@ -1735,7 +1567,7 @@ export class NfTablesProxy {
|
|
|
1735
1567
|
}
|
|
1736
1568
|
|
|
1737
1569
|
// Check if the table has any rules
|
|
1738
|
-
const stdout = await this.executeWithRetry(
|
|
1570
|
+
const stdout = await this.executor.executeWithRetry(
|
|
1739
1571
|
`${NfTablesProxy.NFT_CMD} list table ${family} ${this.tableName}`,
|
|
1740
1572
|
this.settings.maxRetries,
|
|
1741
1573
|
this.settings.retryDelayMs
|
|
@@ -1745,7 +1577,7 @@ export class NfTablesProxy {
|
|
|
1745
1577
|
|
|
1746
1578
|
if (!hasRules) {
|
|
1747
1579
|
// Table is empty, delete it
|
|
1748
|
-
await this.executeWithRetry(
|
|
1580
|
+
await this.executor.executeWithRetry(
|
|
1749
1581
|
`${NfTablesProxy.NFT_CMD} delete table ${family} ${this.tableName}`,
|
|
1750
1582
|
this.settings.maxRetries,
|
|
1751
1583
|
this.settings.retryDelayMs
|
|
@@ -1760,7 +1592,7 @@ export class NfTablesProxy {
|
|
|
1760
1592
|
}
|
|
1761
1593
|
|
|
1762
1594
|
/**
|
|
1763
|
-
* Synchronous version of cleanupEmptyTables
|
|
1595
|
+
* Synchronous version of cleanupEmptyTables (for exit handlers only)
|
|
1764
1596
|
*/
|
|
1765
1597
|
private cleanupEmptyTablesSync(): void {
|
|
1766
1598
|
// Check if tables are empty, and if so, delete them
|
|
@@ -1769,38 +1601,32 @@ export class NfTablesProxy {
|
|
|
1769
1601
|
if (family === 'ip6' && !this.settings.ipv6Support) {
|
|
1770
1602
|
continue;
|
|
1771
1603
|
}
|
|
1772
|
-
|
|
1604
|
+
|
|
1773
1605
|
try {
|
|
1774
1606
|
// Check if table exists
|
|
1775
|
-
const tableExistsOutput = this.
|
|
1776
|
-
`${NfTablesProxy.NFT_CMD} list tables ${family}
|
|
1777
|
-
this.settings.maxRetries,
|
|
1778
|
-
this.settings.retryDelayMs
|
|
1607
|
+
const tableExistsOutput = this.executor.executeSync(
|
|
1608
|
+
`${NfTablesProxy.NFT_CMD} list tables ${family}`
|
|
1779
1609
|
);
|
|
1780
|
-
|
|
1610
|
+
|
|
1781
1611
|
const tableExists = tableExistsOutput.includes(`table ${family} ${this.tableName}`);
|
|
1782
|
-
|
|
1612
|
+
|
|
1783
1613
|
if (!tableExists) {
|
|
1784
1614
|
continue;
|
|
1785
1615
|
}
|
|
1786
|
-
|
|
1616
|
+
|
|
1787
1617
|
// Check if the table has any rules
|
|
1788
|
-
const stdout = this.
|
|
1789
|
-
`${NfTablesProxy.NFT_CMD} list table ${family} ${this.tableName}
|
|
1790
|
-
this.settings.maxRetries,
|
|
1791
|
-
this.settings.retryDelayMs
|
|
1618
|
+
const stdout = this.executor.executeSync(
|
|
1619
|
+
`${NfTablesProxy.NFT_CMD} list table ${family} ${this.tableName}`
|
|
1792
1620
|
);
|
|
1793
|
-
|
|
1621
|
+
|
|
1794
1622
|
const hasRules = stdout.includes('rule');
|
|
1795
|
-
|
|
1623
|
+
|
|
1796
1624
|
if (!hasRules) {
|
|
1797
1625
|
// Table is empty, delete it
|
|
1798
|
-
this.
|
|
1799
|
-
`${NfTablesProxy.NFT_CMD} delete table ${family} ${this.tableName}
|
|
1800
|
-
this.settings.maxRetries,
|
|
1801
|
-
this.settings.retryDelayMs
|
|
1626
|
+
this.executor.executeSync(
|
|
1627
|
+
`${NfTablesProxy.NFT_CMD} delete table ${family} ${this.tableName}`
|
|
1802
1628
|
);
|
|
1803
|
-
|
|
1629
|
+
|
|
1804
1630
|
this.log('info', `Deleted empty table ${family} ${this.tableName}`);
|
|
1805
1631
|
}
|
|
1806
1632
|
} catch (err) {
|