@push.rocks/smartproxy 10.2.0 → 11.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 (60) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/common/port80-adapter.d.ts +11 -0
  3. package/dist_ts/common/port80-adapter.js +61 -0
  4. package/dist_ts/examples/forwarding-example.d.ts +1 -0
  5. package/dist_ts/examples/forwarding-example.js +96 -0
  6. package/dist_ts/index.d.ts +1 -0
  7. package/dist_ts/index.js +3 -1
  8. package/dist_ts/smartproxy/classes.pp.connectionhandler.js +179 -30
  9. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +39 -0
  10. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +172 -20
  11. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +3 -11
  12. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +17 -10
  13. package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +19 -2
  14. package/dist_ts/smartproxy/classes.pp.securitymanager.js +27 -4
  15. package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +3 -3
  16. package/dist_ts/smartproxy/classes.smartproxy.js +45 -13
  17. package/dist_ts/smartproxy/forwarding/domain-config.d.ts +12 -0
  18. package/dist_ts/smartproxy/forwarding/domain-config.js +12 -0
  19. package/dist_ts/smartproxy/forwarding/domain-manager.d.ts +86 -0
  20. package/dist_ts/smartproxy/forwarding/domain-manager.js +241 -0
  21. package/dist_ts/smartproxy/forwarding/forwarding.factory.d.ts +24 -0
  22. package/dist_ts/smartproxy/forwarding/forwarding.factory.js +137 -0
  23. package/dist_ts/smartproxy/forwarding/forwarding.handler.d.ts +55 -0
  24. package/dist_ts/smartproxy/forwarding/forwarding.handler.js +94 -0
  25. package/dist_ts/smartproxy/forwarding/http.handler.d.ts +25 -0
  26. package/dist_ts/smartproxy/forwarding/http.handler.js +123 -0
  27. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.d.ts +24 -0
  28. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.js +154 -0
  29. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.d.ts +36 -0
  30. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.js +229 -0
  31. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.d.ts +35 -0
  32. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.js +254 -0
  33. package/dist_ts/smartproxy/forwarding/index.d.ts +16 -0
  34. package/dist_ts/smartproxy/forwarding/index.js +23 -0
  35. package/dist_ts/smartproxy/types/forwarding.types.d.ts +104 -0
  36. package/dist_ts/smartproxy/types/forwarding.types.js +50 -0
  37. package/package.json +2 -2
  38. package/readme.md +158 -8
  39. package/readme.plan.md +471 -42
  40. package/ts/00_commitinfo_data.ts +1 -1
  41. package/ts/common/port80-adapter.ts +87 -0
  42. package/ts/examples/forwarding-example.ts +128 -0
  43. package/ts/index.ts +3 -0
  44. package/ts/smartproxy/classes.pp.connectionhandler.ts +231 -44
  45. package/ts/smartproxy/classes.pp.domainconfigmanager.ts +198 -24
  46. package/ts/smartproxy/classes.pp.interfaces.ts +3 -11
  47. package/ts/smartproxy/classes.pp.portrangemanager.ts +17 -10
  48. package/ts/smartproxy/classes.pp.securitymanager.ts +29 -5
  49. package/ts/smartproxy/classes.pp.timeoutmanager.ts +3 -3
  50. package/ts/smartproxy/classes.smartproxy.ts +68 -15
  51. package/ts/smartproxy/forwarding/domain-config.ts +28 -0
  52. package/ts/smartproxy/forwarding/domain-manager.ts +283 -0
  53. package/ts/smartproxy/forwarding/forwarding.factory.ts +155 -0
  54. package/ts/smartproxy/forwarding/forwarding.handler.ts +127 -0
  55. package/ts/smartproxy/forwarding/http.handler.ts +140 -0
  56. package/ts/smartproxy/forwarding/https-passthrough.handler.ts +182 -0
  57. package/ts/smartproxy/forwarding/https-terminate-to-http.handler.ts +264 -0
  58. package/ts/smartproxy/forwarding/https-terminate-to-https.handler.ts +292 -0
  59. package/ts/smartproxy/forwarding/index.ts +52 -0
  60. package/ts/smartproxy/types/forwarding.types.ts +162 -0
@@ -0,0 +1,128 @@
1
+ import * as plugins from '../plugins.js';
2
+ import { createServer } from 'http';
3
+ import { Socket } from 'net';
4
+ import {
5
+ DomainManager,
6
+ DomainManagerEvents,
7
+ createDomainConfig,
8
+ helpers
9
+ } from '../smartproxy/forwarding/index.js';
10
+
11
+ /**
12
+ * Example showing how to use the unified forwarding system
13
+ */
14
+ async function main() {
15
+ console.log('Initializing forwarding example...');
16
+
17
+ // Create the domain manager
18
+ const domainManager = new DomainManager();
19
+
20
+ // Set up event listeners
21
+ domainManager.on(DomainManagerEvents.DOMAIN_ADDED, (data) => {
22
+ console.log(`Domain added: ${data.domains.join(', ')} (${data.forwardingType})`);
23
+ });
24
+
25
+ domainManager.on(DomainManagerEvents.DOMAIN_MATCHED, (data) => {
26
+ console.log(`Domain matched: ${data.domain} (${data.handlerType})`);
27
+ });
28
+
29
+ domainManager.on(DomainManagerEvents.DOMAIN_MATCH_FAILED, (data) => {
30
+ console.log(`Domain match failed: ${data.domain}`);
31
+ });
32
+
33
+ domainManager.on(DomainManagerEvents.ERROR, (data) => {
34
+ console.error(`Error:`, data);
35
+ });
36
+
37
+ // Add example domains with different forwarding types
38
+
39
+ // Example 1: HTTP-only forwarding
40
+ await domainManager.addDomainConfig(
41
+ createDomainConfig('example.com', helpers.httpOnly({
42
+ target: { host: 'localhost', port: 3000 }
43
+ }))
44
+ );
45
+
46
+ // Example 2: HTTPS termination with HTTP backend
47
+ await domainManager.addDomainConfig(
48
+ createDomainConfig('secure.example.com', helpers.tlsTerminateToHttp({
49
+ target: { host: 'localhost', port: 3000 }
50
+ }))
51
+ );
52
+
53
+ // Example 3: HTTPS termination with HTTPS backend
54
+ await domainManager.addDomainConfig(
55
+ createDomainConfig('api.example.com', helpers.tlsTerminateToHttps({
56
+ target: { host: 'localhost', port: 8443 }
57
+ }))
58
+ );
59
+
60
+ // Example 4: SNI passthrough
61
+ await domainManager.addDomainConfig(
62
+ createDomainConfig('passthrough.example.com', helpers.sniPassthrough({
63
+ target: { host: '10.0.0.5', port: 443 }
64
+ }))
65
+ );
66
+
67
+ // Example 5: Custom configuration for a more complex setup
68
+ await domainManager.addDomainConfig(
69
+ createDomainConfig(['*.example.com', '*.example.org'], {
70
+ type: 'https-terminate-to-http',
71
+ target: {
72
+ host: ['10.0.0.10', '10.0.0.11'], // Round-robin load balancing
73
+ port: 8080
74
+ },
75
+ http: {
76
+ enabled: true,
77
+ redirectToHttps: false // Allow both HTTP and HTTPS
78
+ },
79
+ acme: {
80
+ enabled: true,
81
+ maintenance: true,
82
+ production: false, // Use staging for testing
83
+ forwardChallenges: {
84
+ host: '192.168.1.100',
85
+ port: 8080
86
+ }
87
+ },
88
+ security: {
89
+ allowedIps: ['10.0.0.*', '192.168.1.*'],
90
+ maxConnections: 100
91
+ },
92
+ advanced: {
93
+ headers: {
94
+ 'X-Forwarded-For': '{clientIp}',
95
+ 'X-Forwarded-Host': '{sni}'
96
+ }
97
+ }
98
+ })
99
+ );
100
+
101
+ // Create a simple HTTP server to demonstrate HTTP handler
102
+ const httpServer = createServer((req, res) => {
103
+ // Extract the domain from the Host header
104
+ const domain = req.headers.host?.split(':')[0] || 'unknown';
105
+
106
+ // Forward the request to the appropriate handler
107
+ if (!domainManager.handleHttpRequest(domain, req, res)) {
108
+ // No handler found, send a default response
109
+ res.statusCode = 404;
110
+ res.end(`No handler found for domain: ${domain}`);
111
+ }
112
+ });
113
+
114
+ // Listen on HTTP port
115
+ httpServer.listen(80, () => {
116
+ console.log('HTTP server listening on port 80');
117
+ });
118
+
119
+ // For HTTPS and SNI, we would need to set up a TLS server
120
+ // This is a simplified example that just shows how the domain manager works
121
+
122
+ console.log('Forwarding example initialized successfully');
123
+ }
124
+
125
+ // Run the example
126
+ main().catch(error => {
127
+ console.error('Error running example:', error);
128
+ });
package/ts/index.ts CHANGED
@@ -7,3 +7,6 @@ export * from './smartproxy/classes.pp.snihandler.js';
7
7
  export * from './smartproxy/classes.pp.interfaces.js';
8
8
 
9
9
  export * from './common/types.js';
10
+
11
+ // Export forwarding system
12
+ export * as forwarding from './smartproxy/forwarding/index.js';
@@ -11,6 +11,8 @@ import { TlsManager } from './classes.pp.tlsmanager.js';
11
11
  import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
12
12
  import { TimeoutManager } from './classes.pp.timeoutmanager.js';
13
13
  import { PortRangeManager } from './classes.pp.portrangemanager.js';
14
+ import type { IForwardingHandler } from './types/forwarding.types.js';
15
+ import type { ForwardingType } from './types/forwarding.types.js';
14
16
 
15
17
  /**
16
18
  * Handles new connection processing and setup logic
@@ -176,37 +178,73 @@ export class ConnectionHandler {
176
178
  destPort: socket.localPort || 0,
177
179
  };
178
180
 
179
- // Extract SNI for domain-specific NetworkProxy handling if available
181
+ // Extract SNI for domain-specific forwarding if available
180
182
  const serverName = this.tlsManager.extractSNI(chunk, connInfo);
181
183
 
182
184
  // For NetworkProxy connections, we'll allow session tickets even without SNI
183
- // We'll only use the serverName if available to determine the specific NetworkProxy port
185
+ // We'll only use the serverName if available to determine the specific forwarding
184
186
  if (serverName) {
185
187
  // Save domain config and SNI in connection record
186
188
  const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
187
189
  record.domainConfig = domainConfig;
188
190
  record.lockedDomain = serverName;
189
191
 
190
- // Use domain-specific NetworkProxy port if configured
191
- if (domainConfig && this.domainConfigManager.shouldUseNetworkProxy(domainConfig)) {
192
- const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
192
+ // If we have a domain config and it has a forwarding config
193
+ if (domainConfig) {
194
+ try {
195
+ // Get the forwarding type for this domain
196
+ const forwardingType = this.domainConfigManager.getForwardingType(domainConfig);
197
+
198
+ // For TLS termination types, use NetworkProxy
199
+ if (forwardingType === 'https-terminate-to-http' ||
200
+ forwardingType === 'https-terminate-to-https') {
201
+ const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
202
+
203
+ if (this.settings.enableDetailedLogging) {
204
+ console.log(
205
+ `[${connectionId}] Using TLS termination (${forwardingType}) for ${serverName} on port ${networkProxyPort}`
206
+ );
207
+ }
208
+
209
+ // Forward to NetworkProxy with domain-specific port
210
+ this.networkProxyBridge.forwardToNetworkProxy(
211
+ connectionId,
212
+ socket,
213
+ record,
214
+ chunk,
215
+ networkProxyPort,
216
+ (reason) => this.connectionManager.initiateCleanupOnce(record, reason)
217
+ );
218
+ return;
219
+ }
193
220
 
194
- if (this.settings.enableDetailedLogging) {
195
- console.log(
196
- `[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
197
- );
198
- }
221
+ // For HTTPS passthrough, use the forwarding handler directly
222
+ if (forwardingType === 'https-passthrough') {
223
+ const handler = this.domainConfigManager.getForwardingHandler(domainConfig);
199
224
 
200
- // Forward to NetworkProxy with domain-specific port
201
- this.networkProxyBridge.forwardToNetworkProxy(
202
- connectionId,
203
- socket,
204
- record,
205
- chunk,
206
- networkProxyPort,
207
- (reason) => this.connectionManager.initiateCleanupOnce(record, reason)
208
- );
209
- return;
225
+ if (this.settings.enableDetailedLogging) {
226
+ console.log(
227
+ `[${connectionId}] Using forwarding handler for SNI passthrough to ${serverName}`
228
+ );
229
+ }
230
+
231
+ // Handle the connection using the handler
232
+ handler.handleConnection(socket);
233
+
234
+ return;
235
+ }
236
+
237
+ // For HTTP-only, we shouldn't get TLS connections
238
+ if (forwardingType === 'http-only') {
239
+ console.log(`[${connectionId}] Received TLS connection for HTTP-only domain ${serverName}`);
240
+ socket.end();
241
+ this.connectionManager.cleanupConnection(record, 'wrong_protocol');
242
+ return;
243
+ }
244
+ } catch (err) {
245
+ console.log(`[${connectionId}] Error using forwarding handler: ${err}`);
246
+ // Fall through to default NetworkProxy handling
247
+ }
210
248
  }
211
249
  } else if (
212
250
  this.settings.allowSessionTicket === false &&
@@ -229,10 +267,38 @@ export class ConnectionHandler {
229
267
  (reason) => this.connectionManager.initiateCleanupOnce(record, reason)
230
268
  );
231
269
  } else {
232
- // If not TLS, use normal direct connection
270
+ // If not TLS, handle as plain HTTP
233
271
  console.log(
234
272
  `[${connectionId}] Non-TLS connection on NetworkProxy port ${record.localPort}`
235
273
  );
274
+
275
+ // Check if we have a domain config based on port
276
+ const portBasedDomainConfig = this.domainConfigManager.findDomainConfigForPort(record.localPort);
277
+
278
+ if (portBasedDomainConfig) {
279
+ try {
280
+ // If this domain supports HTTP via a forwarding handler, use it
281
+ if (this.domainConfigManager.supportsHttp(portBasedDomainConfig)) {
282
+ const handler = this.domainConfigManager.getForwardingHandler(portBasedDomainConfig);
283
+
284
+ if (this.settings.enableDetailedLogging) {
285
+ console.log(
286
+ `[${connectionId}] Using forwarding handler for non-TLS connection to port ${record.localPort}`
287
+ );
288
+ }
289
+
290
+ // Handle the connection using the handler
291
+ handler.handleConnection(socket);
292
+
293
+ return;
294
+ }
295
+ } catch (err) {
296
+ console.log(`[${connectionId}] Error using forwarding handler for HTTP: ${err}`);
297
+ // Fall through to direct connection
298
+ }
299
+ }
300
+
301
+ // Use legacy direct connection as fallback
236
302
  this.setupDirectConnection(socket, record, undefined, undefined, chunk);
237
303
  }
238
304
  });
@@ -380,9 +446,8 @@ export class ConnectionHandler {
380
446
  if (domainConfig) {
381
447
  const ipRules = this.domainConfigManager.getEffectiveIPRules(domainConfig);
382
448
 
383
- // Skip IP validation if allowedIPs is empty
449
+ // Perform IP validation using security rules
384
450
  if (
385
- domainConfig.allowedIPs.length > 0 &&
386
451
  !this.securityManager.isIPAuthorized(
387
452
  record.remoteIP,
388
453
  ipRules.allowedIPs,
@@ -431,10 +496,31 @@ export class ConnectionHandler {
431
496
  // Only apply port-based rules if the incoming port is within one of the global port ranges.
432
497
  if (this.portRangeManager.isPortInGlobalRanges(localPort)) {
433
498
  if (this.portRangeManager.shouldUseGlobalForwarding(localPort)) {
499
+ // Create a virtual domain config for global forwarding with security settings
500
+ const globalDomainConfig = {
501
+ domains: ['global'],
502
+ forwarding: {
503
+ type: 'http-only' as ForwardingType,
504
+ target: {
505
+ host: this.settings.targetIP!,
506
+ port: this.settings.toPort
507
+ },
508
+ security: {
509
+ allowedIps: this.settings.defaultAllowedIPs || [],
510
+ blockedIps: this.settings.defaultBlockedIPs || []
511
+ }
512
+ },
513
+ };
514
+
515
+ // Use the same IP filtering mechanism as domain-specific configs
516
+ const ipRules = this.domainConfigManager.getEffectiveIPRules(globalDomainConfig);
517
+
434
518
  if (
435
- this.settings.defaultAllowedIPs &&
436
- this.settings.defaultAllowedIPs.length > 0 &&
437
- !this.securityManager.isIPAuthorized(record.remoteIP, this.settings.defaultAllowedIPs)
519
+ !this.securityManager.isIPAuthorized(
520
+ record.remoteIP,
521
+ ipRules.allowedIPs,
522
+ ipRules.blockedIPs
523
+ )
438
524
  ) {
439
525
  console.log(
440
526
  `[${connectionId}] Connection from ${record.remoteIP} rejected: IP ${record.remoteIP} not allowed in global default allowed list.`
@@ -442,29 +528,21 @@ export class ConnectionHandler {
442
528
  socket.end();
443
529
  return;
444
530
  }
531
+
445
532
  if (this.settings.enableDetailedLogging) {
446
533
  console.log(
447
534
  `[${connectionId}] Port-based connection from ${record.remoteIP} on port ${localPort} forwarded to global target IP ${this.settings.targetIP}.`
448
535
  );
449
536
  }
450
- setupConnection(
451
- '',
452
- undefined,
453
- {
454
- domains: ['global'],
455
- allowedIPs: this.settings.defaultAllowedIPs || [],
456
- blockedIPs: this.settings.defaultBlockedIPs || [],
457
- targetIPs: [this.settings.targetIP!],
458
- portRanges: [],
459
- },
460
- localPort
461
- );
537
+
538
+ setupConnection('', undefined, globalDomainConfig, localPort);
462
539
  return;
463
540
  } else {
464
541
  // Attempt to find a matching forced domain config based on the local port.
465
542
  const forcedDomain = this.domainConfigManager.findDomainConfigForPort(localPort);
466
543
 
467
544
  if (forcedDomain) {
545
+ // Get effective IP rules from the domain config's forwarding security settings
468
546
  const ipRules = this.domainConfigManager.getEffectiveIPRules(forcedDomain);
469
547
 
470
548
  if (
@@ -624,10 +702,18 @@ export class ConnectionHandler {
624
702
  initialDataReceived = true;
625
703
  record.hasReceivedInitialData = true;
626
704
 
627
- if (
628
- this.settings.defaultAllowedIPs &&
629
- this.settings.defaultAllowedIPs.length > 0 &&
630
- !this.securityManager.isIPAuthorized(record.remoteIP, this.settings.defaultAllowedIPs)
705
+ // Create default security settings for non-SNI connections
706
+ const defaultSecurity = {
707
+ allowedIPs: this.settings.defaultAllowedIPs || [],
708
+ blockedIPs: this.settings.defaultBlockedIPs || []
709
+ };
710
+
711
+ if (defaultSecurity.allowedIPs.length > 0 &&
712
+ !this.securityManager.isIPAuthorized(
713
+ record.remoteIP,
714
+ defaultSecurity.allowedIPs,
715
+ defaultSecurity.blockedIPs
716
+ )
631
717
  ) {
632
718
  return rejectIncomingConnection(
633
719
  'rejected',
@@ -652,13 +738,99 @@ export class ConnectionHandler {
652
738
  ): void {
653
739
  const connectionId = record.id;
654
740
 
741
+ // If we have a domain config, try to use a forwarding handler
742
+ if (domainConfig) {
743
+ try {
744
+ // Get the forwarding handler for this domain
745
+ const forwardingHandler = this.domainConfigManager.getForwardingHandler(domainConfig);
746
+
747
+ // Check the forwarding type to determine how to handle the connection
748
+ const forwardingType = this.domainConfigManager.getForwardingType(domainConfig);
749
+
750
+ // For TLS connections, handle differently based on forwarding type
751
+ if (record.isTLS) {
752
+ // For HTTP-only, we shouldn't get TLS connections
753
+ if (forwardingType === 'http-only') {
754
+ console.log(`[${connectionId}] Received TLS connection for HTTP-only domain ${serverName || 'unknown'}`);
755
+ socket.end();
756
+ this.connectionManager.initiateCleanupOnce(record, 'wrong_protocol');
757
+ return;
758
+ }
759
+
760
+ // For HTTPS passthrough, use the handler's connection handling
761
+ if (forwardingType === 'https-passthrough') {
762
+ // If there's initial data, process it first
763
+ if (initialChunk) {
764
+ record.bytesReceived += initialChunk.length;
765
+ }
766
+
767
+ // Let the handler take over
768
+ if (this.settings.enableDetailedLogging) {
769
+ console.log(`[${connectionId}] Using forwarding handler for ${forwardingType} connection to ${serverName || 'unknown'}`);
770
+ }
771
+
772
+ // Pass the connection to the handler
773
+ forwardingHandler.handleConnection(socket);
774
+
775
+ // Set metadata fields
776
+ record.usingNetworkProxy = false;
777
+
778
+ // Add connection information to record
779
+ if (serverName) {
780
+ record.lockedDomain = serverName;
781
+ }
782
+
783
+ return;
784
+ }
785
+
786
+ // For TLS termination types, we'll fall through to the legacy connection setup
787
+ // because NetworkProxy is used for termination
788
+ }
789
+ // For non-TLS connections, check if we support HTTP
790
+ else if (!record.isTLS && this.domainConfigManager.supportsHttp(domainConfig)) {
791
+ // For HTTP handling that the handler supports natively
792
+ if (forwardingType === 'http-only' ||
793
+ (forwardingType === 'https-terminate-to-http' || forwardingType === 'https-terminate-to-https')) {
794
+
795
+ // If there's redirect to HTTPS configured and this is a normal HTTP connection
796
+ if (this.domainConfigManager.shouldRedirectToHttps(domainConfig)) {
797
+ // We'll let the handler deal with the HTTP request and potential redirect
798
+ // Once an HTTP request arrives, it can redirect as needed
799
+ }
800
+
801
+ // Let the handler take over for HTTP handling
802
+ if (this.settings.enableDetailedLogging) {
803
+ console.log(`[${connectionId}] Using forwarding handler for HTTP connection to ${serverName || 'unknown'}`);
804
+ }
805
+
806
+ // Pass the connection to the handler
807
+ forwardingHandler.handleConnection(socket);
808
+
809
+ // Add connection information to record
810
+ if (serverName) {
811
+ record.lockedDomain = serverName;
812
+ }
813
+
814
+ return;
815
+ }
816
+ }
817
+ } catch (err) {
818
+ console.log(`[${connectionId}] Error using forwarding handler: ${err}`);
819
+ // Fall through to legacy connection handling
820
+ }
821
+ }
822
+
823
+ // If we get here, we'll use legacy connection handling
824
+
655
825
  // Determine target host
656
826
  const targetHost = domainConfig
657
827
  ? this.domainConfigManager.getTargetIP(domainConfig)
658
828
  : this.settings.targetIP!;
659
829
 
660
- // Determine target port
661
- const targetPort = overridePort !== undefined ? overridePort : this.settings.toPort;
830
+ // Determine target port - first try forwarding config, then fallback
831
+ const targetPort = domainConfig
832
+ ? this.domainConfigManager.getTargetPort(domainConfig, overridePort !== undefined ? overridePort : this.settings.toPort)
833
+ : (overridePort !== undefined ? overridePort : this.settings.toPort);
662
834
 
663
835
  // Setup connection options
664
836
  const connectionOptions: plugins.net.NetConnectOpts = {
@@ -842,6 +1014,21 @@ export class ConnectionHandler {
842
1014
  this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
843
1015
  }
844
1016
 
1017
+ // If we have a forwarding handler for this domain, let it handle the error
1018
+ if (domainConfig) {
1019
+ try {
1020
+ const forwardingHandler = this.domainConfigManager.getForwardingHandler(domainConfig);
1021
+ forwardingHandler.emit('connection_error', {
1022
+ socket,
1023
+ error: err,
1024
+ connectionId
1025
+ });
1026
+ } catch (handlerErr) {
1027
+ // If getting the handler fails, just log and continue with normal cleanup
1028
+ console.log(`Error getting forwarding handler for error handling: ${handlerErr}`);
1029
+ }
1030
+ }
1031
+
845
1032
  // Clean up the connection
846
1033
  this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
847
1034
  });