@push.rocks/smartproxy 19.6.17 → 20.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist_ts/core/utils/shared-security-manager.js +30 -5
  2. package/dist_ts/proxies/http-proxy/request-handler.d.ts +4 -0
  3. package/dist_ts/proxies/http-proxy/request-handler.js +104 -21
  4. package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +4 -0
  5. package/dist_ts/proxies/http-proxy/websocket-handler.js +78 -8
  6. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +19 -2
  7. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  8. package/dist_ts/proxies/smart-proxy/nftables-manager.js +14 -11
  9. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -0
  10. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +112 -28
  11. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +23 -23
  12. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +13 -13
  13. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +4 -7
  14. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +41 -25
  15. package/package.json +1 -1
  16. package/readme.plan.md +139 -266
  17. package/ts/core/utils/shared-security-manager.ts +33 -4
  18. package/ts/proxies/http-proxy/request-handler.ts +124 -21
  19. package/ts/proxies/http-proxy/websocket-handler.ts +96 -8
  20. package/ts/proxies/smart-proxy/models/route-types.ts +34 -8
  21. package/ts/proxies/smart-proxy/nftables-manager.ts +14 -10
  22. package/ts/proxies/smart-proxy/route-connection-handler.ts +132 -28
  23. package/ts/proxies/smart-proxy/utils/route-helpers.ts +14 -14
  24. package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -6
  25. package/ts/proxies/smart-proxy/utils/route-utils.ts +3 -6
  26. package/ts/proxies/smart-proxy/utils/route-validators.ts +38 -21
@@ -10,7 +10,7 @@ import { ConnectionPool } from './connection-pool.js';
10
10
  import { ContextCreator } from './context-creator.js';
11
11
  import { HttpRequestHandler } from './http-request-handler.js';
12
12
  import { Http2RequestHandler } from './http2-request-handler.js';
13
- import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
13
+ import type { IRouteConfig, IRouteTarget } from '../smart-proxy/models/route-types.js';
14
14
  import type { IRouteContext, IHttpRouteContext } from '../../core/models/route-context.js';
15
15
  import { toBaseContext } from '../../core/models/route-context.js';
16
16
  import { TemplateUtils } from '../../core/utils/template-utils.js';
@@ -99,6 +99,80 @@ export class RequestHandler {
99
99
  return { ...this.defaultHeaders };
100
100
  }
101
101
 
102
+ /**
103
+ * Select the appropriate target from the targets array based on sub-matching criteria
104
+ */
105
+ private selectTarget(
106
+ targets: IRouteTarget[],
107
+ context: {
108
+ port: number;
109
+ path?: string;
110
+ headers?: Record<string, string>;
111
+ method?: string;
112
+ }
113
+ ): IRouteTarget | null {
114
+ // Sort targets by priority (higher first)
115
+ const sortedTargets = [...targets].sort((a, b) => (b.priority || 0) - (a.priority || 0));
116
+
117
+ // Find the first matching target
118
+ for (const target of sortedTargets) {
119
+ if (!target.match) {
120
+ // No match criteria means this is a default/fallback target
121
+ return target;
122
+ }
123
+
124
+ // Check port match
125
+ if (target.match.ports && !target.match.ports.includes(context.port)) {
126
+ continue;
127
+ }
128
+
129
+ // Check path match (supports wildcards)
130
+ if (target.match.path && context.path) {
131
+ const pathPattern = target.match.path.replace(/\*/g, '.*');
132
+ const pathRegex = new RegExp(`^${pathPattern}$`);
133
+ if (!pathRegex.test(context.path)) {
134
+ continue;
135
+ }
136
+ }
137
+
138
+ // Check method match
139
+ if (target.match.method && context.method && !target.match.method.includes(context.method)) {
140
+ continue;
141
+ }
142
+
143
+ // Check headers match
144
+ if (target.match.headers && context.headers) {
145
+ let headersMatch = true;
146
+ for (const [key, pattern] of Object.entries(target.match.headers)) {
147
+ const headerValue = context.headers[key.toLowerCase()];
148
+ if (!headerValue) {
149
+ headersMatch = false;
150
+ break;
151
+ }
152
+
153
+ if (pattern instanceof RegExp) {
154
+ if (!pattern.test(headerValue)) {
155
+ headersMatch = false;
156
+ break;
157
+ }
158
+ } else if (headerValue !== pattern) {
159
+ headersMatch = false;
160
+ break;
161
+ }
162
+ }
163
+ if (!headersMatch) {
164
+ continue;
165
+ }
166
+ }
167
+
168
+ // All criteria matched
169
+ return target;
170
+ }
171
+
172
+ // No matching target found, return the first target without match criteria (default)
173
+ return sortedTargets.find(t => !t.match) || null;
174
+ }
175
+
102
176
  /**
103
177
  * Apply CORS headers to response if configured
104
178
  * Implements Phase 5.5: Context-aware CORS handling
@@ -480,17 +554,31 @@ export class RequestHandler {
480
554
  }
481
555
  }
482
556
 
483
- // If we found a matching route with function-based targets, use it
484
- if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.target) {
557
+ // If we found a matching route with forward action, select appropriate target
558
+ if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.targets && matchingRoute.action.targets.length > 0) {
485
559
  this.logger.debug(`Found matching route: ${matchingRoute.name || 'unnamed'}`);
486
560
 
561
+ // Select the appropriate target from the targets array
562
+ const selectedTarget = this.selectTarget(matchingRoute.action.targets, {
563
+ port: routeContext.port,
564
+ path: routeContext.path,
565
+ headers: routeContext.headers,
566
+ method: routeContext.method
567
+ });
568
+
569
+ if (!selectedTarget) {
570
+ this.logger.error(`No matching target found for route ${matchingRoute.name}`);
571
+ req.socket.end();
572
+ return;
573
+ }
574
+
487
575
  // Extract target information, resolving functions if needed
488
576
  let targetHost: string | string[];
489
577
  let targetPort: number;
490
578
 
491
579
  try {
492
580
  // Check function cache for host and resolve or use cached value
493
- if (typeof matchingRoute.action.target.host === 'function') {
581
+ if (typeof selectedTarget.host === 'function') {
494
582
  // Generate a function ID for caching (use route name or ID if available)
495
583
  const functionId = `host-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
496
584
 
@@ -502,7 +590,7 @@ export class RequestHandler {
502
590
  this.logger.debug(`Using cached host value for ${functionId}`);
503
591
  } else {
504
592
  // Resolve the function and cache the result
505
- const resolvedHost = matchingRoute.action.target.host(toBaseContext(routeContext));
593
+ const resolvedHost = selectedTarget.host(toBaseContext(routeContext));
506
594
  targetHost = resolvedHost;
507
595
 
508
596
  // Cache the result
@@ -511,16 +599,16 @@ export class RequestHandler {
511
599
  }
512
600
  } else {
513
601
  // No cache available, just resolve
514
- const resolvedHost = matchingRoute.action.target.host(routeContext);
602
+ const resolvedHost = selectedTarget.host(routeContext);
515
603
  targetHost = resolvedHost;
516
604
  this.logger.debug(`Resolved function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
517
605
  }
518
606
  } else {
519
- targetHost = matchingRoute.action.target.host;
607
+ targetHost = selectedTarget.host;
520
608
  }
521
609
 
522
610
  // Check function cache for port and resolve or use cached value
523
- if (typeof matchingRoute.action.target.port === 'function') {
611
+ if (typeof selectedTarget.port === 'function') {
524
612
  // Generate a function ID for caching
525
613
  const functionId = `port-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
526
614
 
@@ -532,7 +620,7 @@ export class RequestHandler {
532
620
  this.logger.debug(`Using cached port value for ${functionId}`);
533
621
  } else {
534
622
  // Resolve the function and cache the result
535
- const resolvedPort = matchingRoute.action.target.port(toBaseContext(routeContext));
623
+ const resolvedPort = selectedTarget.port(toBaseContext(routeContext));
536
624
  targetPort = resolvedPort;
537
625
 
538
626
  // Cache the result
@@ -541,12 +629,12 @@ export class RequestHandler {
541
629
  }
542
630
  } else {
543
631
  // No cache available, just resolve
544
- const resolvedPort = matchingRoute.action.target.port(routeContext);
632
+ const resolvedPort = selectedTarget.port(routeContext);
545
633
  targetPort = resolvedPort;
546
634
  this.logger.debug(`Resolved function-based port to: ${resolvedPort}`);
547
635
  }
548
636
  } else {
549
- targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number;
637
+ targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port as number;
550
638
  }
551
639
 
552
640
  // Select a single host if an array was provided
@@ -626,17 +714,32 @@ export class RequestHandler {
626
714
  }
627
715
  }
628
716
 
629
- // If we found a matching route with function-based targets, use it
630
- if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.target) {
717
+ // If we found a matching route with forward action, select appropriate target
718
+ if (matchingRoute && matchingRoute.action.type === 'forward' && matchingRoute.action.targets && matchingRoute.action.targets.length > 0) {
631
719
  this.logger.debug(`Found matching route for HTTP/2 request: ${matchingRoute.name || 'unnamed'}`);
632
720
 
721
+ // Select the appropriate target from the targets array
722
+ const selectedTarget = this.selectTarget(matchingRoute.action.targets, {
723
+ port: routeContext.port,
724
+ path: routeContext.path,
725
+ headers: routeContext.headers,
726
+ method: routeContext.method
727
+ });
728
+
729
+ if (!selectedTarget) {
730
+ this.logger.error(`No matching target found for route ${matchingRoute.name}`);
731
+ stream.respond({ ':status': 502 });
732
+ stream.end();
733
+ return;
734
+ }
735
+
633
736
  // Extract target information, resolving functions if needed
634
737
  let targetHost: string | string[];
635
738
  let targetPort: number;
636
739
 
637
740
  try {
638
741
  // Check function cache for host and resolve or use cached value
639
- if (typeof matchingRoute.action.target.host === 'function') {
742
+ if (typeof selectedTarget.host === 'function') {
640
743
  // Generate a function ID for caching (use route name or ID if available)
641
744
  const functionId = `host-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
642
745
 
@@ -648,7 +751,7 @@ export class RequestHandler {
648
751
  this.logger.debug(`Using cached host value for HTTP/2: ${functionId}`);
649
752
  } else {
650
753
  // Resolve the function and cache the result
651
- const resolvedHost = matchingRoute.action.target.host(toBaseContext(routeContext));
754
+ const resolvedHost = selectedTarget.host(toBaseContext(routeContext));
652
755
  targetHost = resolvedHost;
653
756
 
654
757
  // Cache the result
@@ -657,16 +760,16 @@ export class RequestHandler {
657
760
  }
658
761
  } else {
659
762
  // No cache available, just resolve
660
- const resolvedHost = matchingRoute.action.target.host(routeContext);
763
+ const resolvedHost = selectedTarget.host(routeContext);
661
764
  targetHost = resolvedHost;
662
765
  this.logger.debug(`Resolved HTTP/2 function-based host to: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
663
766
  }
664
767
  } else {
665
- targetHost = matchingRoute.action.target.host;
768
+ targetHost = selectedTarget.host;
666
769
  }
667
770
 
668
771
  // Check function cache for port and resolve or use cached value
669
- if (typeof matchingRoute.action.target.port === 'function') {
772
+ if (typeof selectedTarget.port === 'function') {
670
773
  // Generate a function ID for caching
671
774
  const functionId = `port-http2-${matchingRoute.id || matchingRoute.name || 'unnamed'}`;
672
775
 
@@ -678,7 +781,7 @@ export class RequestHandler {
678
781
  this.logger.debug(`Using cached port value for HTTP/2: ${functionId}`);
679
782
  } else {
680
783
  // Resolve the function and cache the result
681
- const resolvedPort = matchingRoute.action.target.port(toBaseContext(routeContext));
784
+ const resolvedPort = selectedTarget.port(toBaseContext(routeContext));
682
785
  targetPort = resolvedPort;
683
786
 
684
787
  // Cache the result
@@ -687,12 +790,12 @@ export class RequestHandler {
687
790
  }
688
791
  } else {
689
792
  // No cache available, just resolve
690
- const resolvedPort = matchingRoute.action.target.port(routeContext);
793
+ const resolvedPort = selectedTarget.port(routeContext);
691
794
  targetPort = resolvedPort;
692
795
  this.logger.debug(`Resolved HTTP/2 function-based port to: ${resolvedPort}`);
693
796
  }
694
797
  } else {
695
- targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number;
798
+ targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port as number;
696
799
  }
697
800
 
698
801
  // Select a single host if an array was provided
@@ -3,7 +3,7 @@ import '../../core/models/socket-augmentation.js';
3
3
  import { type IHttpProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger } from './models/types.js';
4
4
  import { ConnectionPool } from './connection-pool.js';
5
5
  import { HttpRouter } from '../../routing/router/index.js';
6
- import type { IRouteConfig } from '../smart-proxy/models/route-types.js';
6
+ import type { IRouteConfig, IRouteTarget } from '../smart-proxy/models/route-types.js';
7
7
  import type { IRouteContext } from '../../core/models/route-context.js';
8
8
  import { toBaseContext } from '../../core/models/route-context.js';
9
9
  import { ContextCreator } from './context-creator.js';
@@ -53,6 +53,80 @@ export class WebSocketHandler {
53
53
  this.securityManager.setRoutes(routes);
54
54
  }
55
55
 
56
+ /**
57
+ * Select the appropriate target from the targets array based on sub-matching criteria
58
+ */
59
+ private selectTarget(
60
+ targets: IRouteTarget[],
61
+ context: {
62
+ port: number;
63
+ path?: string;
64
+ headers?: Record<string, string>;
65
+ method?: string;
66
+ }
67
+ ): IRouteTarget | null {
68
+ // Sort targets by priority (higher first)
69
+ const sortedTargets = [...targets].sort((a, b) => (b.priority || 0) - (a.priority || 0));
70
+
71
+ // Find the first matching target
72
+ for (const target of sortedTargets) {
73
+ if (!target.match) {
74
+ // No match criteria means this is a default/fallback target
75
+ return target;
76
+ }
77
+
78
+ // Check port match
79
+ if (target.match.ports && !target.match.ports.includes(context.port)) {
80
+ continue;
81
+ }
82
+
83
+ // Check path match (supports wildcards)
84
+ if (target.match.path && context.path) {
85
+ const pathPattern = target.match.path.replace(/\*/g, '.*');
86
+ const pathRegex = new RegExp(`^${pathPattern}$`);
87
+ if (!pathRegex.test(context.path)) {
88
+ continue;
89
+ }
90
+ }
91
+
92
+ // Check method match
93
+ if (target.match.method && context.method && !target.match.method.includes(context.method)) {
94
+ continue;
95
+ }
96
+
97
+ // Check headers match
98
+ if (target.match.headers && context.headers) {
99
+ let headersMatch = true;
100
+ for (const [key, pattern] of Object.entries(target.match.headers)) {
101
+ const headerValue = context.headers[key.toLowerCase()];
102
+ if (!headerValue) {
103
+ headersMatch = false;
104
+ break;
105
+ }
106
+
107
+ if (pattern instanceof RegExp) {
108
+ if (!pattern.test(headerValue)) {
109
+ headersMatch = false;
110
+ break;
111
+ }
112
+ } else if (headerValue !== pattern) {
113
+ headersMatch = false;
114
+ break;
115
+ }
116
+ }
117
+ if (!headersMatch) {
118
+ continue;
119
+ }
120
+ }
121
+
122
+ // All criteria matched
123
+ return target;
124
+ }
125
+
126
+ // No matching target found, return the first target without match criteria (default)
127
+ return sortedTargets.find(t => !t.match) || null;
128
+ }
129
+
56
130
  /**
57
131
  * Initialize WebSocket server on an existing HTTPS server
58
132
  */
@@ -146,9 +220,23 @@ export class WebSocketHandler {
146
220
  let destination: { host: string; port: number };
147
221
 
148
222
  // If we found a route with the modern router, use it
149
- if (route && route.action.type === 'forward' && route.action.target) {
223
+ if (route && route.action.type === 'forward' && route.action.targets && route.action.targets.length > 0) {
150
224
  this.logger.debug(`Found matching WebSocket route: ${route.name || 'unnamed'}`);
151
225
 
226
+ // Select the appropriate target from the targets array
227
+ const selectedTarget = this.selectTarget(route.action.targets, {
228
+ port: routeContext.port,
229
+ path: routeContext.path,
230
+ headers: routeContext.headers,
231
+ method: routeContext.method
232
+ });
233
+
234
+ if (!selectedTarget) {
235
+ this.logger.error(`No matching target found for route ${route.name}`);
236
+ wsIncoming.close(1003, 'No matching target');
237
+ return;
238
+ }
239
+
152
240
  // Check if WebSockets are enabled for this route
153
241
  if (route.action.websocket?.enabled === false) {
154
242
  this.logger.debug(`WebSockets are disabled for route: ${route.name || 'unnamed'}`);
@@ -192,20 +280,20 @@ export class WebSocketHandler {
192
280
 
193
281
  try {
194
282
  // Resolve host if it's a function
195
- if (typeof route.action.target.host === 'function') {
196
- const resolvedHost = route.action.target.host(toBaseContext(routeContext));
283
+ if (typeof selectedTarget.host === 'function') {
284
+ const resolvedHost = selectedTarget.host(toBaseContext(routeContext));
197
285
  targetHost = resolvedHost;
198
286
  this.logger.debug(`Resolved function-based host for WebSocket: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`);
199
287
  } else {
200
- targetHost = route.action.target.host;
288
+ targetHost = selectedTarget.host;
201
289
  }
202
290
 
203
291
  // Resolve port if it's a function
204
- if (typeof route.action.target.port === 'function') {
205
- targetPort = route.action.target.port(toBaseContext(routeContext));
292
+ if (typeof selectedTarget.port === 'function') {
293
+ targetPort = selectedTarget.port(toBaseContext(routeContext));
206
294
  this.logger.debug(`Resolved function-based port for WebSocket: ${targetPort}`);
207
295
  } else {
208
- targetPort = route.action.target.port === 'preserve' ? routeContext.port : route.action.target.port as number;
296
+ targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port as number;
209
297
  }
210
298
 
211
299
  // Select a single host if an array was provided
@@ -46,11 +46,36 @@ export interface IRouteMatch {
46
46
 
47
47
 
48
48
  /**
49
- * Target configuration for forwarding
49
+ * Target-specific match criteria for sub-routing within a route
50
+ */
51
+ export interface ITargetMatch {
52
+ ports?: number[]; // Match specific ports from the route
53
+ path?: string; // Match specific paths (supports wildcards like /api/*)
54
+ headers?: Record<string, string | RegExp>; // Match specific HTTP headers
55
+ method?: string[]; // Match specific HTTP methods (GET, POST, etc.)
56
+ }
57
+
58
+ /**
59
+ * Target configuration for forwarding with sub-matching and overrides
50
60
  */
51
61
  export interface IRouteTarget {
62
+ // Optional sub-matching criteria within the route
63
+ match?: ITargetMatch;
64
+
65
+ // Target destination
52
66
  host: string | string[] | ((context: IRouteContext) => string | string[]); // Host or hosts with optional function for dynamic resolution
53
67
  port: number | 'preserve' | ((context: IRouteContext) => number); // Port with optional function for dynamic mapping (use 'preserve' to keep the incoming port)
68
+
69
+ // Optional target-specific overrides (these override route-level settings)
70
+ tls?: IRouteTls; // Override route-level TLS settings
71
+ websocket?: IRouteWebSocket; // Override route-level WebSocket settings
72
+ loadBalancing?: IRouteLoadBalancing; // Override route-level load balancing
73
+ sendProxyProtocol?: boolean; // Override route-level proxy protocol setting
74
+ headers?: IRouteHeaders; // Override route-level headers
75
+ advanced?: IRouteAdvanced; // Override route-level advanced settings
76
+
77
+ // Priority for matching (higher values are checked first, default: 0)
78
+ priority?: number;
54
79
  }
55
80
 
56
81
  /**
@@ -221,19 +246,20 @@ export interface IRouteAction {
221
246
  // Basic routing
222
247
  type: TRouteActionType;
223
248
 
224
- // Target for forwarding
225
- target?: IRouteTarget;
249
+ // Targets for forwarding (array supports multiple targets with sub-matching)
250
+ // Required for 'forward' action type
251
+ targets?: IRouteTarget[];
226
252
 
227
- // TLS handling
253
+ // TLS handling (default for all targets, can be overridden per target)
228
254
  tls?: IRouteTls;
229
255
 
230
- // WebSocket support
256
+ // WebSocket support (default for all targets, can be overridden per target)
231
257
  websocket?: IRouteWebSocket;
232
258
 
233
- // Load balancing options
259
+ // Load balancing options (default for all targets, can be overridden per target)
234
260
  loadBalancing?: IRouteLoadBalancing;
235
261
 
236
- // Advanced options
262
+ // Advanced options (default for all targets, can be overridden per target)
237
263
  advanced?: IRouteAdvanced;
238
264
 
239
265
  // Additional options for backend-specific settings
@@ -251,7 +277,7 @@ export interface IRouteAction {
251
277
  // Socket handler function (when type is 'socket-handler')
252
278
  socketHandler?: TSocketHandler;
253
279
 
254
- // PROXY protocol support
280
+ // PROXY protocol support (default for all targets, can be overridden per target)
255
281
  sendProxyProtocol?: boolean;
256
282
  }
257
283
 
@@ -123,39 +123,43 @@ export class NFTablesManager {
123
123
  private createNfTablesOptions(route: IRouteConfig): NfTableProxyOptions {
124
124
  const { action } = route;
125
125
 
126
- // Ensure we have a target
127
- if (!action.target) {
128
- throw new Error('Route must have a target to use NFTables forwarding');
126
+ // Ensure we have targets
127
+ if (!action.targets || action.targets.length === 0) {
128
+ throw new Error('Route must have targets to use NFTables forwarding');
129
129
  }
130
130
 
131
+ // NFTables can only handle a single target, so we use the first target without match criteria
132
+ // or the first target if all have match criteria
133
+ const defaultTarget = action.targets.find(t => !t.match) || action.targets[0];
134
+
131
135
  // Convert port specifications
132
136
  const fromPorts = this.expandPortRange(route.match.ports);
133
137
 
134
138
  // Determine target port
135
139
  let toPorts: number | PortRange | Array<number | PortRange>;
136
140
 
137
- if (action.target.port === 'preserve') {
141
+ if (defaultTarget.port === 'preserve') {
138
142
  // 'preserve' means use the same ports as the source
139
143
  toPorts = fromPorts;
140
- } else if (typeof action.target.port === 'function') {
144
+ } else if (typeof defaultTarget.port === 'function') {
141
145
  // For function-based ports, we can't determine at setup time
142
146
  // Use the "preserve" approach and let NFTables handle it
143
147
  toPorts = fromPorts;
144
148
  } else {
145
- toPorts = action.target.port;
149
+ toPorts = defaultTarget.port;
146
150
  }
147
151
 
148
152
  // Determine target host
149
153
  let toHost: string;
150
- if (typeof action.target.host === 'function') {
154
+ if (typeof defaultTarget.host === 'function') {
151
155
  // Can't determine at setup time, use localhost as a placeholder
152
156
  // and rely on run-time handling
153
157
  toHost = 'localhost';
154
- } else if (Array.isArray(action.target.host)) {
158
+ } else if (Array.isArray(defaultTarget.host)) {
155
159
  // Use first host for now - NFTables will do simple round-robin
156
- toHost = action.target.host[0];
160
+ toHost = defaultTarget.host[0];
157
161
  } else {
158
- toHost = action.target.host;
162
+ toHost = defaultTarget.host;
159
163
  }
160
164
 
161
165
  // Create options