@push.rocks/smartproxy 18.0.0 → 18.0.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.
Files changed (35) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/core/utils/route-utils.d.ts +3 -3
  3. package/dist_ts/core/utils/route-utils.js +9 -9
  4. package/dist_ts/proxies/network-proxy/http-request-handler.js +3 -2
  5. package/dist_ts/proxies/nftables-proxy/models/interfaces.d.ts +2 -2
  6. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +21 -21
  7. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -0
  8. package/dist_ts/proxies/smart-proxy/index.js +2 -1
  9. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +1 -0
  10. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +14 -0
  11. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  12. package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +82 -0
  13. package/dist_ts/proxies/smart-proxy/nftables-manager.js +235 -0
  14. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +42 -1
  15. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +6 -1
  16. package/dist_ts/proxies/smart-proxy/smart-proxy.js +46 -2
  17. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -0
  18. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -2
  19. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +77 -0
  20. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +119 -1
  21. package/package.json +4 -4
  22. package/readme.plan.md +618 -110
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/core/utils/route-utils.ts +9 -9
  25. package/ts/proxies/network-proxy/http-request-handler.ts +3 -2
  26. package/ts/proxies/nftables-proxy/models/interfaces.ts +2 -2
  27. package/ts/proxies/nftables-proxy/nftables-proxy.ts +20 -20
  28. package/ts/proxies/smart-proxy/index.ts +1 -0
  29. package/ts/proxies/smart-proxy/models/interfaces.ts +3 -0
  30. package/ts/proxies/smart-proxy/models/route-types.ts +20 -0
  31. package/ts/proxies/smart-proxy/nftables-manager.ts +268 -0
  32. package/ts/proxies/smart-proxy/route-connection-handler.ts +55 -0
  33. package/ts/proxies/smart-proxy/smart-proxy.ts +60 -1
  34. package/ts/proxies/smart-proxy/utils/index.ts +2 -1
  35. package/ts/proxies/smart-proxy/utils/route-helpers.ts +192 -0
package/readme.plan.md CHANGED
@@ -1,146 +1,654 @@
1
- # SmartProxy Interface Consolidation Plan
1
+ # NFTables-SmartProxy Integration Plan
2
2
 
3
3
  ## Overview
4
4
 
5
- This document outlines a plan to consolidate duplicate and inconsistent interfaces in the SmartProxy codebase, specifically the `IRouteSecurity` interface which is defined twice with different properties. This inconsistency caused issues with security checks for port forwarding. The goal is to unify these interfaces, use consistent property naming, and improve code maintainability.
5
+ This document outlines a comprehensive plan to integrate the existing NFTables functionality with the SmartProxy core to provide advanced network-level routing capabilities. The NFTables proxy already exists in the codebase but is not fully integrated with the SmartProxy routing system. This integration will allow SmartProxy to leverage the power of Linux's NFTables firewall system for high-performance port forwarding, load balancing, and security filtering.
6
6
 
7
- ## Problem Description
7
+ ## Current State
8
8
 
9
- We currently have two separate `IRouteSecurity` interfaces defined in `ts/proxies/smart-proxy/models/route-types.ts`:
9
+ 1. **NFTablesProxy**: A standalone implementation exists in `ts/proxies/nftables-proxy/` with its own configuration and API.
10
+ 2. **SmartProxy**: The main routing system with route-based configuration.
11
+ 3. **No Integration**: Currently, these systems operate independently with no shared configuration or coordination.
10
12
 
11
- 1. **First definition** (lines 116-122) - Used in IRouteAction:
13
+ ## Goals
14
+
15
+ 1. Create a unified configuration system where SmartProxy routes can specify NFTables-based forwarding.
16
+ 2. Allow SmartProxy to dynamically provision and manage NFTables rules based on route configuration.
17
+ 3. Support advanced filtering and security rules through NFTables for better performance.
18
+ 4. Ensure backward compatibility with existing setups.
19
+ 5. Provide metrics integration between the systems.
20
+
21
+ ## Implementation Plan
22
+
23
+ ### Phase 1: Route Configuration Schema Extension
24
+
25
+ 1. **Extend Route Configuration Schema**:
26
+ - Add new `forwardingEngine` option to IRouteAction to specify the forwarding implementation.
27
+ - Support values: 'node' (current NodeJS implementation) and 'nftables' (Linux NFTables).
28
+ - Add NFTables-specific configuration options to IRouteAction.
29
+
30
+ 2. **Update Type Definitions**:
12
31
  ```typescript
13
- export interface IRouteSecurity {
14
- allowedIps?: string[];
15
- blockedIps?: string[];
16
- maxConnections?: number;
17
- authentication?: IRouteAuthentication;
32
+ // In route-types.ts
33
+ export interface IRouteAction {
34
+ type: 'forward' | 'redirect' | 'block';
35
+ target?: IRouteTarget;
36
+ security?: IRouteSecurity;
37
+ options?: IRouteOptions;
38
+ tls?: IRouteTlsOptions;
39
+ forwardingEngine?: 'node' | 'nftables'; // New field
40
+ nftables?: INfTablesOptions; // New field
41
+ }
42
+
43
+ export interface INfTablesOptions {
44
+ preserveSourceIP?: boolean;
45
+ protocol?: 'tcp' | 'udp' | 'all';
46
+ maxRate?: string; // QoS rate limiting
47
+ priority?: number; // QoS priority
48
+ tableName?: string; // Optional custom table name
49
+ useIPSets?: boolean; // Use IP sets for performance
50
+ useAdvancedNAT?: boolean; // Use connection tracking
18
51
  }
19
52
  ```
20
53
 
21
- 2. **Second definition** (lines 253-272) - Used directly in IRouteConfig:
54
+ ### Phase 2: NFTablesManager Implementation
55
+
56
+ 1. **Create NFTablesManager Class**:
57
+ - Create a new class to manage NFTables rules based on SmartProxy routes.
58
+ - Add methods to create, update, and remove NFTables rules.
59
+ - Design a rule naming scheme to track which rules correspond to which routes.
60
+
61
+ 2. **Implementation**:
22
62
  ```typescript
23
- export interface IRouteSecurity {
24
- rateLimit?: IRouteRateLimit;
25
- basicAuth?: {...};
26
- jwtAuth?: {...};
27
- ipAllowList?: string[];
28
- ipBlockList?: string[];
63
+ // In ts/proxies/smart-proxy/nftables-manager.ts
64
+ export class NFTablesManager {
65
+ private rulesMap: Map<string, NfTablesProxy> = new Map();
66
+
67
+ constructor(private options: ISmartProxyOptions) {}
68
+
69
+ /**
70
+ * Provision NFTables rules for a route
71
+ */
72
+ public async provisionRoute(route: IRouteConfig): Promise<boolean> {
73
+ // Generate a unique ID for this route
74
+ const routeId = this.generateRouteId(route);
75
+
76
+ // Skip if route doesn't use NFTables
77
+ if (route.action.forwardingEngine !== 'nftables') {
78
+ return true;
79
+ }
80
+
81
+ // Create NFTables options from route configuration
82
+ const nftOptions = this.createNfTablesOptions(route);
83
+
84
+ // Create and start an NFTablesProxy instance
85
+ const proxy = new NfTablesProxy(nftOptions);
86
+
87
+ try {
88
+ await proxy.start();
89
+ this.rulesMap.set(routeId, proxy);
90
+ return true;
91
+ } catch (err) {
92
+ console.error(`Failed to provision NFTables rules for route ${route.name}: ${err.message}`);
93
+ return false;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Remove NFTables rules for a route
99
+ */
100
+ public async deprovisionRoute(route: IRouteConfig): Promise<boolean> {
101
+ const routeId = this.generateRouteId(route);
102
+
103
+ const proxy = this.rulesMap.get(routeId);
104
+ if (!proxy) {
105
+ return true; // Nothing to remove
106
+ }
107
+
108
+ try {
109
+ await proxy.stop();
110
+ this.rulesMap.delete(routeId);
111
+ return true;
112
+ } catch (err) {
113
+ console.error(`Failed to deprovision NFTables rules for route ${route.name}: ${err.message}`);
114
+ return false;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Update NFTables rules when route changes
120
+ */
121
+ public async updateRoute(oldRoute: IRouteConfig, newRoute: IRouteConfig): Promise<boolean> {
122
+ // Remove old rules and add new ones
123
+ await this.deprovisionRoute(oldRoute);
124
+ return this.provisionRoute(newRoute);
125
+ }
126
+
127
+ /**
128
+ * Generate a unique ID for a route
129
+ */
130
+ private generateRouteId(route: IRouteConfig): string {
131
+ // Generate a unique ID based on route properties
132
+ return `${route.name || 'unnamed'}-${JSON.stringify(route.match)}-${Date.now()}`;
133
+ }
134
+
135
+ /**
136
+ * Create NFTablesProxy options from a route configuration
137
+ */
138
+ private createNfTablesOptions(route: IRouteConfig): NfTableProxyOptions {
139
+ const { action } = route;
140
+
141
+ // Ensure we have a target
142
+ if (!action.target) {
143
+ throw new Error('Route must have a target to use NFTables forwarding');
144
+ }
145
+
146
+ // Convert port specifications
147
+ const fromPorts = this.expandPortRange(route.match.ports);
148
+
149
+ // Determine target port
150
+ let toPorts;
151
+ if (action.target.port === 'preserve') {
152
+ // 'preserve' means use the same ports as the source
153
+ toPorts = fromPorts;
154
+ } else if (typeof action.target.port === 'function') {
155
+ // For function-based ports, we can't determine at setup time
156
+ // Use the "preserve" approach and let NFTables handle it
157
+ toPorts = fromPorts;
158
+ } else {
159
+ toPorts = action.target.port;
160
+ }
161
+
162
+ // Create options
163
+ const options: NfTableProxyOptions = {
164
+ fromPort: fromPorts,
165
+ toPort: toPorts,
166
+ toHost: typeof action.target.host === 'function'
167
+ ? 'localhost' // Can't determine at setup time, use localhost
168
+ : (Array.isArray(action.target.host)
169
+ ? action.target.host[0] // Use first host for now
170
+ : action.target.host),
171
+ protocol: action.nftables?.protocol || 'tcp',
172
+ preserveSourceIP: action.nftables?.preserveSourceIP,
173
+ useIPSets: action.nftables?.useIPSets !== false,
174
+ useAdvancedNAT: action.nftables?.useAdvancedNAT,
175
+ enableLogging: this.options.enableDetailedLogging,
176
+ deleteOnExit: true,
177
+ tableName: action.nftables?.tableName || 'smartproxy'
178
+ };
179
+
180
+ // Add security-related options
181
+ if (action.security?.ipAllowList?.length) {
182
+ options.allowedSourceIPs = action.security.ipAllowList;
183
+ }
184
+
185
+ if (action.security?.ipBlockList?.length) {
186
+ options.bannedSourceIPs = action.security.ipBlockList;
187
+ }
188
+
189
+ // Add QoS options
190
+ if (action.nftables?.maxRate || action.nftables?.priority) {
191
+ options.qos = {
192
+ enabled: true,
193
+ maxRate: action.nftables.maxRate,
194
+ priority: action.nftables.priority
195
+ };
196
+ }
197
+
198
+ return options;
199
+ }
200
+
201
+ /**
202
+ * Expand port range specifications
203
+ */
204
+ private expandPortRange(ports: TPortRange): number | PortRange | Array<number | PortRange> {
205
+ // Use RouteManager's expandPortRange to convert to actual port numbers
206
+ const routeManager = new RouteManager(this.options);
207
+
208
+ // Process different port specifications
209
+ if (typeof ports === 'number') {
210
+ return ports;
211
+ } else if (Array.isArray(ports)) {
212
+ const result: Array<number | PortRange> = [];
213
+
214
+ for (const item of ports) {
215
+ if (typeof item === 'number') {
216
+ result.push(item);
217
+ } else if ('from' in item && 'to' in item) {
218
+ result.push({ from: item.from, to: item.to });
219
+ }
220
+ }
221
+
222
+ return result;
223
+ } else if ('from' in ports && 'to' in ports) {
224
+ return { from: ports.from, to: ports.to };
225
+ }
226
+
227
+ // Fallback
228
+ return 80;
229
+ }
230
+
231
+ /**
232
+ * Get status of all managed rules
233
+ */
234
+ public async getStatus(): Promise<Record<string, NfTablesStatus>> {
235
+ const result: Record<string, NfTablesStatus> = {};
236
+
237
+ for (const [routeId, proxy] of this.rulesMap.entries()) {
238
+ result[routeId] = await proxy.getStatus();
239
+ }
240
+
241
+ return result;
242
+ }
243
+
244
+ /**
245
+ * Stop all NFTables rules
246
+ */
247
+ public async stop(): Promise<void> {
248
+ // Stop all NFTables proxies
249
+ const stopPromises = Array.from(this.rulesMap.values()).map(proxy => proxy.stop());
250
+ await Promise.all(stopPromises);
251
+
252
+ this.rulesMap.clear();
253
+ }
29
254
  }
30
255
  ```
31
256
 
32
- This duplication with inconsistent naming (`allowedIps` vs `ipAllowList` and `blockedIps` vs `ipBlockList`) caused routing issues when IP security checks were used, as we had to implement a workaround to check both property names.
257
+ ### Phase 3: SmartProxy Integration
33
258
 
34
- ## Implementation Plan
259
+ 1. **Extend SmartProxy Class**:
260
+ - Add NFTablesManager as a property of SmartProxy.
261
+ - Hook into route configuration to provision NFTables rules.
262
+ - Add methods to manage NFTables functionality.
35
263
 
36
- ### Phase 1: Interface Consolidation
264
+ 2. **Implementation**:
265
+ ```typescript
266
+ // In ts/proxies/smart-proxy/smart-proxy.ts
267
+ import { NFTablesManager } from './nftables-manager.js';
37
268
 
38
- 1. **Create a unified interface definition:**
39
- - Create one comprehensive `IRouteSecurity` interface that includes all properties
40
- - Use consistent property naming (standardize on `ipAllowList` and `ipBlockList`)
41
- - Add proper documentation for each property
42
- - Remove the duplicate interface definition
43
-
44
- 2. **Update references to use the unified interface:**
45
- - Update all code that references the old interface properties
46
- - Update all configurations to use the new property names
47
- - Ensure implementation in `route-manager.ts` uses the correct property names
269
+ export class SmartProxy {
270
+ // Existing properties
271
+ private nftablesManager: NFTablesManager;
272
+
273
+ constructor(options: ISmartProxyOptions) {
274
+ // Existing initialization
275
+
276
+ // Initialize NFTablesManager
277
+ this.nftablesManager = new NFTablesManager(options);
278
+ }
279
+
280
+ /**
281
+ * Start the SmartProxy server
282
+ */
283
+ public async start(): Promise<void> {
284
+ // Existing initialization
285
+
286
+ // If we have routes, provision NFTables rules for them
287
+ for (const route of this.settings.routes) {
288
+ if (route.action.forwardingEngine === 'nftables') {
289
+ await this.nftablesManager.provisionRoute(route);
290
+ }
291
+ }
292
+
293
+ // Rest of existing start method
294
+ }
295
+
296
+ /**
297
+ * Stop the SmartProxy server
298
+ */
299
+ public async stop(): Promise<void> {
300
+ // Stop NFTablesManager first
301
+ await this.nftablesManager.stop();
302
+
303
+ // Rest of existing stop method
304
+ }
305
+
306
+ /**
307
+ * Update routes
308
+ */
309
+ public async updateRoutes(routes: IRouteConfig[]): Promise<void> {
310
+ // Get existing routes that use NFTables
311
+ const oldNfTablesRoutes = this.settings.routes.filter(
312
+ r => r.action.forwardingEngine === 'nftables'
313
+ );
314
+
315
+ // Get new routes that use NFTables
316
+ const newNfTablesRoutes = routes.filter(
317
+ r => r.action.forwardingEngine === 'nftables'
318
+ );
319
+
320
+ // Find routes to remove, update, or add
321
+ for (const oldRoute of oldNfTablesRoutes) {
322
+ const newRoute = newNfTablesRoutes.find(r => r.name === oldRoute.name);
323
+
324
+ if (!newRoute) {
325
+ // Route was removed
326
+ await this.nftablesManager.deprovisionRoute(oldRoute);
327
+ } else {
328
+ // Route was updated
329
+ await this.nftablesManager.updateRoute(oldRoute, newRoute);
330
+ }
331
+ }
332
+
333
+ // Find new routes to add
334
+ for (const newRoute of newNfTablesRoutes) {
335
+ const oldRoute = oldNfTablesRoutes.find(r => r.name === newRoute.name);
336
+
337
+ if (!oldRoute) {
338
+ // New route
339
+ await this.nftablesManager.provisionRoute(newRoute);
340
+ }
341
+ }
342
+
343
+ // Update settings with the new routes
344
+ this.settings.routes = routes;
345
+
346
+ // Update route manager with new routes
347
+ this.routeManager.updateRoutes(routes);
348
+ }
349
+
350
+ /**
351
+ * Get NFTables status
352
+ */
353
+ public async getNfTablesStatus(): Promise<Record<string, NfTablesStatus>> {
354
+ return this.nftablesManager.getStatus();
355
+ }
356
+ }
357
+ ```
48
358
 
49
- ### Phase 2: Code and Documentation Updates
359
+ ### Phase 4: Routing System Integration
50
360
 
51
- 1. **Update type usages and documentation:**
52
- - Update all code that creates or uses security configurations
53
- - Update documentation to reflect the new interface structure
54
- - Add examples of the correct property usage
55
- - Document the breaking change in changelog.md
361
+ 1. **Extend the Route-Connection-Handler**:
362
+ - Modify to check if a route uses NFTables.
363
+ - Skip Node.js-based connection handling for NFTables routes.
56
364
 
57
- 2. **Add tests:**
58
- - Update existing tests to use the new property names
59
- - Add test cases for all security configuration scenarios
60
- - Verify that port range configurations with security settings work correctly
365
+ 2. **Implementation**:
366
+ ```typescript
367
+ // In ts/proxies/smart-proxy/route-connection-handler.ts
368
+ export class RouteConnectionHandler {
369
+ // Existing methods
370
+
371
+ /**
372
+ * Route the connection based on match criteria
373
+ */
374
+ private routeConnection(
375
+ socket: plugins.net.Socket,
376
+ record: IConnectionRecord,
377
+ serverName: string,
378
+ initialChunk?: Buffer
379
+ ): void {
380
+ // Find matching route
381
+ const routeMatch = this.routeManager.findMatchingRoute({
382
+ port: record.localPort,
383
+ domain: serverName,
384
+ clientIp: record.remoteIP,
385
+ path: undefined,
386
+ tlsVersion: undefined
387
+ });
388
+
389
+ if (!routeMatch) {
390
+ // Existing code for no matching route
391
+ return;
392
+ }
393
+
394
+ const route = routeMatch.route;
395
+
396
+ // Check if this route uses NFTables for forwarding
397
+ if (route.action.forwardingEngine === 'nftables') {
398
+ // For NFTables routes, we don't need to do anything at the application level
399
+ // The packet is forwarded at the kernel level
400
+
401
+ // Log the connection
402
+ console.log(
403
+ `[${record.id}] Connection forwarded by NFTables: ${record.remoteIP} -> port ${record.localPort}`
404
+ );
405
+
406
+ // Just close the socket in our application since it's handled at kernel level
407
+ socket.end();
408
+ this.connectionManager.initiateCleanupOnce(record, 'nftables_handled');
409
+ return;
410
+ }
411
+
412
+ // Existing code for handling the route
413
+ }
414
+ }
415
+ ```
416
+
417
+ ### Phase 5: CLI and Configuration Helpers
61
418
 
62
- ## Implementation Steps
419
+ 1. **Add Helper Functions**:
420
+ - Create helper functions for easy route creation with NFTables.
421
+ - Update the route-helpers.ts utility file.
422
+
423
+ 2. **Implementation**:
424
+ ```typescript
425
+ // In ts/proxies/smart-proxy/utils/route-helpers.ts
426
+
427
+ /**
428
+ * Create an NFTables-based route
429
+ */
430
+ export function createNfTablesRoute(
431
+ nameOrDomains: string | string[],
432
+ target: { host: string; port: number | 'preserve' },
433
+ options: {
434
+ ports?: TPortRange;
435
+ protocol?: 'tcp' | 'udp' | 'all';
436
+ preserveSourceIP?: boolean;
437
+ allowedIps?: string[];
438
+ maxRate?: string;
439
+ priority?: number;
440
+ useTls?: boolean;
441
+ } = {}
442
+ ): IRouteConfig {
443
+ // Determine if this is a name or domain
444
+ let name: string;
445
+ let domains: string | string[];
446
+
447
+ if (Array.isArray(nameOrDomains) || nameOrDomains.includes('.')) {
448
+ domains = nameOrDomains;
449
+ name = Array.isArray(nameOrDomains) ? nameOrDomains[0] : nameOrDomains;
450
+ } else {
451
+ name = nameOrDomains;
452
+ domains = []; // No domains
453
+ }
454
+
455
+ const route: IRouteConfig = {
456
+ name,
457
+ match: {
458
+ domains,
459
+ ports: options.ports || 80
460
+ },
461
+ action: {
462
+ type: 'forward',
463
+ target: {
464
+ host: target.host,
465
+ port: target.port
466
+ },
467
+ forwardingEngine: 'nftables',
468
+ nftables: {
469
+ protocol: options.protocol || 'tcp',
470
+ preserveSourceIP: options.preserveSourceIP,
471
+ maxRate: options.maxRate,
472
+ priority: options.priority
473
+ }
474
+ }
475
+ };
476
+
477
+ // Add security if allowed IPs are specified
478
+ if (options.allowedIps?.length) {
479
+ route.action.security = {
480
+ ipAllowList: options.allowedIps
481
+ };
482
+ }
483
+
484
+ // Add TLS options if needed
485
+ if (options.useTls) {
486
+ route.action.tls = {
487
+ mode: 'passthrough'
488
+ };
489
+ }
490
+
491
+ return route;
492
+ }
493
+
494
+ /**
495
+ * Create an NFTables-based TLS termination route
496
+ */
497
+ export function createNfTablesTerminateRoute(
498
+ nameOrDomains: string | string[],
499
+ target: { host: string; port: number | 'preserve' },
500
+ options: {
501
+ ports?: TPortRange;
502
+ protocol?: 'tcp' | 'udp' | 'all';
503
+ preserveSourceIP?: boolean;
504
+ allowedIps?: string[];
505
+ maxRate?: string;
506
+ priority?: number;
507
+ certificate?: string | { cert: string; key: string };
508
+ } = {}
509
+ ): IRouteConfig {
510
+ const route = createNfTablesRoute(
511
+ nameOrDomains,
512
+ target,
513
+ {
514
+ ...options,
515
+ ports: options.ports || 443,
516
+ useTls: false
517
+ }
518
+ );
519
+
520
+ // Set TLS termination
521
+ route.action.tls = {
522
+ mode: 'terminate',
523
+ certificate: options.certificate || 'auto'
524
+ };
525
+
526
+ return route;
527
+ }
528
+ ```
529
+
530
+ ### Phase 6: Documentation and Testing
531
+
532
+ 1. **Update Documentation**:
533
+ - Add NFTables integration documentation to README and API docs.
534
+ - Document the implementation and use cases.
535
+
536
+ 2. **Test Cases**:
537
+ - Create test cases for NFTables-based routing.
538
+ - Test performance comparison with Node.js-based forwarding.
539
+ - Test security features with IP allowlists/blocklists.
63
540
 
64
541
  ```typescript
65
- // Step 1: Define the unified interface
66
- export interface IRouteSecurity {
67
- // Access control lists
68
- ipAllowList?: string[]; // IP addresses that are allowed to connect
69
- ipBlockList?: string[]; // IP addresses that are blocked from connecting
70
-
71
- // Connection limits
72
- maxConnections?: number; // Maximum concurrent connections
73
-
74
- // Authentication
75
- authentication?: IRouteAuthentication;
542
+ // In test/test.nftables-integration.ts
543
+ import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
544
+ import { createNfTablesRoute } from '../ts/proxies/smart-proxy/utils/route-helpers.js';
545
+ import { expect, tap } from '@push.rocks/tapbundle';
546
+ import * as net from 'net';
547
+
548
+ // Test server and client utilities
549
+ let testServer: net.Server;
550
+ let smartProxy: SmartProxy;
551
+
552
+ const TEST_PORT = 4000;
553
+ const PROXY_PORT = 5000;
554
+ const TEST_DATA = 'Hello through NFTables!';
555
+
556
+ tap.test('setup NFTables integration test environment', async () => {
557
+ // Create a test TCP server
558
+ testServer = net.createServer((socket) => {
559
+ socket.on('data', (data) => {
560
+ socket.write(`Server says: ${data.toString()}`);
561
+ });
562
+ });
76
563
 
77
- // Rate limiting
78
- rateLimit?: IRouteRateLimit;
564
+ await new Promise<void>((resolve) => {
565
+ testServer.listen(TEST_PORT, () => {
566
+ console.log(`Test server listening on port ${TEST_PORT}`);
567
+ resolve();
568
+ });
569
+ });
79
570
 
80
- // Authentication methods
81
- basicAuth?: {
82
- enabled: boolean;
83
- users: Array<{ username: string; password: string }>;
84
- realm?: string;
85
- excludePaths?: string[];
86
- };
571
+ // Create SmartProxy with NFTables route
572
+ smartProxy = new SmartProxy({
573
+ routes: [
574
+ createNfTablesRoute('test-nftables', {
575
+ host: 'localhost',
576
+ port: TEST_PORT
577
+ }, {
578
+ ports: PROXY_PORT,
579
+ protocol: 'tcp'
580
+ })
581
+ ]
582
+ });
87
583
 
88
- jwtAuth?: {
89
- enabled: boolean;
90
- secret: string;
91
- algorithm?: string;
92
- issuer?: string;
93
- audience?: string;
94
- expiresIn?: number;
95
- excludePaths?: string[];
96
- };
97
- }
98
- ```
99
-
100
- Update `isClientIpAllowed` method to use only the new property names:
584
+ // Start the proxy
585
+ await smartProxy.start();
586
+ });
101
587
 
102
- ```typescript
103
- private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
104
- const security = route.action.security;
105
-
106
- if (!security) {
107
- return true; // No security settings means allowed
108
- }
588
+ tap.test('should forward TCP connections through NFTables', async () => {
589
+ // Connect to the proxy port
590
+ const client = new net.Socket();
109
591
 
110
- // Check blocked IPs first
111
- if (security.ipBlockList && security.ipBlockList.length > 0) {
112
- for (const pattern of security.ipBlockList) {
113
- if (this.matchIpPattern(pattern, clientIp)) {
114
- return false; // IP is blocked
115
- }
116
- }
117
- }
592
+ const response = await new Promise<string>((resolve, reject) => {
593
+ let responseData = '';
594
+
595
+ client.connect(PROXY_PORT, 'localhost', () => {
596
+ client.write(TEST_DATA);
597
+ });
598
+
599
+ client.on('data', (data) => {
600
+ responseData += data.toString();
601
+ client.end();
602
+ });
603
+
604
+ client.on('end', () => {
605
+ resolve(responseData);
606
+ });
607
+
608
+ client.on('error', (err) => {
609
+ reject(err);
610
+ });
611
+ });
118
612
 
119
- // If there are allowed IPs, check them
120
- if (security.ipAllowList && security.ipAllowList.length > 0) {
121
- for (const pattern of security.ipAllowList) {
122
- if (this.matchIpPattern(pattern, clientIp)) {
123
- return true; // IP is allowed
124
- }
125
- }
126
- return false; // IP not in allowed list
127
- }
613
+ expect(response).toEqual(`Server says: ${TEST_DATA}`);
614
+ });
615
+
616
+ tap.test('cleanup NFTables integration test environment', async () => {
617
+ // Stop the proxy and test server
618
+ await smartProxy.stop();
128
619
 
129
- // No allowed IPs specified, so IP is allowed
130
- return true;
131
- }
620
+ await new Promise<void>((resolve) => {
621
+ testServer.close(() => {
622
+ resolve();
623
+ });
624
+ });
625
+ });
626
+
627
+ export default tap.start();
132
628
  ```
133
629
 
134
630
  ## Expected Benefits
135
631
 
136
- - **Improved Consistency**: Single, unified interface with consistent property naming
137
- - **Better Type Safety**: Eliminating confusing duplicate interface definitions
138
- - **Reduced Errors**: Prevent misunderstandings about which property names to use
139
- - **Forward Compatibility**: Clearer path for future security enhancements
140
- - **Better Developer Experience**: Simplified interface with comprehensive documentation
632
+ 1. **Performance**: NFTables operates at the kernel level, offering much higher performance than Node.js-based routing.
633
+ 2. **Scalability**: Handle more connections with less CPU and memory usage.
634
+ 3. **Security**: Leverage kernel-level security features for better protection.
635
+ 4. **Integration**: Unified configuration model between application and network layers.
636
+ 5. **Advanced Features**: Support for QoS, rate limiting, and other advanced networking features.
637
+
638
+ ## Implementation Notes
639
+
640
+ - This integration requires root/sudo access to configure NFTables rules.
641
+ - Consider adding a capability check to gracefully fall back to Node.js routing if NFTables is not available.
642
+ - The NFTables integration should be optional and SmartProxy should continue to work without it.
643
+ - The integration provides a path for future extensions to other kernel-level networking features.
644
+
645
+ ## Timeline
141
646
 
142
- ## Testing Plan
647
+ - Phase 1 (Route Configuration Schema): 1-2 days
648
+ - Phase 2 (NFTablesManager): 2-3 days
649
+ - Phase 3 (SmartProxy Integration): 1-2 days
650
+ - Phase 4 (Routing System Integration): 1 day
651
+ - Phase 5 (CLI and Helpers): 1 day
652
+ - Phase 6 (Documentation and Testing): 2 days
143
653
 
144
- 1. Test with existing configurations using both old and new property names
145
- 2. Create specific test cases for port ranges with different security configurations
146
- 3. Verify that port forwarding with IP allow lists works correctly with the unified interface
654
+ **Total Estimated Time: 8-11 days**