@push.rocks/smartproxy 18.0.1 → 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 (33) 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/nftables-proxy/models/interfaces.d.ts +2 -2
  5. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +21 -21
  6. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -0
  7. package/dist_ts/proxies/smart-proxy/index.js +2 -1
  8. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +1 -0
  9. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +14 -0
  10. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  11. package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +82 -0
  12. package/dist_ts/proxies/smart-proxy/nftables-manager.js +235 -0
  13. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +42 -1
  14. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +6 -1
  15. package/dist_ts/proxies/smart-proxy/smart-proxy.js +46 -2
  16. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -0
  17. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -2
  18. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +77 -0
  19. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +119 -1
  20. package/package.json +2 -2
  21. package/readme.plan.md +616 -148
  22. package/ts/00_commitinfo_data.ts +1 -1
  23. package/ts/core/utils/route-utils.ts +9 -9
  24. package/ts/proxies/nftables-proxy/models/interfaces.ts +2 -2
  25. package/ts/proxies/nftables-proxy/nftables-proxy.ts +20 -20
  26. package/ts/proxies/smart-proxy/index.ts +1 -0
  27. package/ts/proxies/smart-proxy/models/interfaces.ts +3 -0
  28. package/ts/proxies/smart-proxy/models/route-types.ts +20 -0
  29. package/ts/proxies/smart-proxy/nftables-manager.ts +268 -0
  30. package/ts/proxies/smart-proxy/route-connection-handler.ts +55 -0
  31. package/ts/proxies/smart-proxy/smart-proxy.ts +60 -1
  32. package/ts/proxies/smart-proxy/utils/index.ts +2 -1
  33. package/ts/proxies/smart-proxy/utils/route-helpers.ts +192 -0
package/readme.plan.md CHANGED
@@ -1,186 +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 (RESOLVED)
7
+ ## Current State
8
8
 
9
- We had two separate `IRouteSecurity` interfaces defined in `ts/proxies/smart-proxy/models/route-types.ts` which have now been consolidated into a single interface:
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** (previous 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** (previous 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, particularly with port range configurations.
257
+ ### Phase 3: SmartProxy Integration
33
258
 
34
- ## Implementation Plan (COMPLETED)
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
- - Created one comprehensive `IRouteSecurity` interface that includes all properties
40
- - Standardized on `ipAllowList` and `ipBlockList` property names
41
- - Added proper documentation for each property
42
- - Removed the duplicate interface definition
43
-
44
- 2. **Update references to use the unified interface:** ✅
45
- - Updated all code that references the old interface properties
46
- - Updated all configurations to use the new property names
47
- - Ensured 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
- - Updated all code that creates or uses security configurations
53
- - Updated documentation to reflect the new interface structure
54
- - Added examples of the correct property usage
55
- - Documented the changes in this plan
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. **Fix TypeScript errors:** ✅
58
- - Fixed TypeScript errors in http-request-handler.ts
59
- - Successfully built the project with `pnpm run build`
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
+ ```
60
416
 
61
- ## Implementation Completed
417
+ ### Phase 5: CLI and Configuration Helpers
62
418
 
63
- The interface consolidation has been successfully implemented with the following changes:
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.
64
540
 
65
- 1. **Unified interface created:**
66
541
  ```typescript
67
- // Consolidated interface definition
68
- export interface IRouteSecurity {
69
- // Access control lists
70
- ipAllowList?: string[]; // IP addresses that are allowed to connect
71
- ipBlockList?: string[]; // IP addresses that are blocked from connecting
72
-
73
- // Connection limits
74
- maxConnections?: number; // Maximum concurrent connections
75
-
76
- // Authentication
77
- 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
+ });
78
563
 
79
- // Rate limiting
80
- 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
+ });
81
570
 
82
- // Authentication methods
83
- basicAuth?: {
84
- enabled: boolean;
85
- users: Array<{ username: string; password: string }>;
86
- realm?: string;
87
- excludePaths?: string[];
88
- };
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
+ });
89
583
 
90
- jwtAuth?: {
91
- enabled: boolean;
92
- secret: string;
93
- algorithm?: string;
94
- issuer?: string;
95
- audience?: string;
96
- expiresIn?: number;
97
- excludePaths?: string[];
98
- };
99
- }
100
- ```
584
+ // Start the proxy
585
+ await smartProxy.start();
586
+ });
101
587
 
102
- 2. **Updated isClientIpAllowed method:**
103
- ```typescript
104
- private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
105
- const security = route.action.security;
106
-
107
- if (!security) {
108
- return true; // No security settings means allowed
109
- }
588
+ tap.test('should forward TCP connections through NFTables', async () => {
589
+ // Connect to the proxy port
590
+ const client = new net.Socket();
110
591
 
111
- // Check blocked IPs first
112
- if (security.ipBlockList && security.ipBlockList.length > 0) {
113
- for (const pattern of security.ipBlockList) {
114
- if (this.matchIpPattern(pattern, clientIp)) {
115
- return false; // IP is blocked
116
- }
117
- }
118
- }
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
+ });
119
612
 
120
- // If there are allowed IPs, check them
121
- if (security.ipAllowList && security.ipAllowList.length > 0) {
122
- for (const pattern of security.ipAllowList) {
123
- if (this.matchIpPattern(pattern, clientIp)) {
124
- return true; // IP is allowed
125
- }
126
- }
127
- return false; // IP not in allowed list
128
- }
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();
129
619
 
130
- // No allowed IPs specified, so IP is allowed
131
- return true;
132
- }
133
- ```
620
+ await new Promise<void>((resolve) => {
621
+ testServer.close(() => {
622
+ resolve();
623
+ });
624
+ });
625
+ });
134
626
 
135
- 3. **Fixed port preservation logic:**
136
- ```typescript
137
- // In base-handler.ts
138
- protected resolvePort(
139
- port: number | 'preserve' | ((ctx: any) => number),
140
- incomingPort: number = 80
141
- ): number {
142
- if (typeof port === 'function') {
143
- try {
144
- // Create a minimal context for the function that includes the incoming port
145
- const ctx = { port: incomingPort };
146
- return port(ctx);
147
- } catch (err) {
148
- console.error('Error resolving port function:', err);
149
- return incomingPort; // Fall back to incoming port
150
- }
151
- } else if (port === 'preserve') {
152
- return incomingPort; // Use the actual incoming port for 'preserve'
153
- } else {
154
- return port;
155
- }
156
- }
627
+ export default tap.start();
157
628
  ```
158
629
 
159
- 4. **Fixed TypeScript error in http-request-handler.ts:**
160
- ```typescript
161
- // Safely check for host property existence
162
- if (options.headers && 'host' in options.headers) {
163
- // Only apply if host header rewrite is enabled or not explicitly disabled
164
- const shouldRewriteHost = route?.action.options?.rewriteHostHeader !== false;
165
- if (shouldRewriteHost) {
166
- // Safely cast to OutgoingHttpHeaders to access host property
167
- (options.headers as plugins.http.OutgoingHttpHeaders).host = `${destination.host}:${destination.port}`;
168
- }
169
- }
170
- ```
630
+ ## Expected Benefits
631
+
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
171
639
 
172
- ## Achieved Benefits
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.
173
644
 
174
- - **Improved Consistency**: Single, unified interface with consistent property naming
175
- - **Better Type Safety**: Eliminated confusing duplicate interface definitions
176
- - **Reduced Errors**: Prevented misunderstandings about which property names to use
177
- - **Forward Compatibility**: Clearer path for future security enhancements
178
- - **Better Developer Experience**: Simplified interface with comprehensive documentation
179
- - **Fixed Issues**: Port preservation with port ranges now works correctly with security checks
645
+ ## Timeline
180
646
 
181
- ## Verification
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
182
653
 
183
- - The project builds successfully with `pnpm run build`
184
- - The unified interface works properly with all type checking
185
- - The port range forwarding with `port: 'preserve'` now works correctly with IP security rules
186
- - The security checks consistently use the standardized property names throughout the codebase
654
+ **Total Estimated Time: 8-11 days**