@push.rocks/smartproxy 23.0.0 → 23.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +17 -0
- package/dist_rust/{rustproxy → rustproxy_linux_amd64} +0 -0
- package/dist_rust/rustproxy_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +9 -21
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +84 -212
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +2 -3
- package/npmextra.json +3 -0
- package/package.json +13 -11
- package/readme.md +41 -11
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/plugins.ts +2 -0
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +103 -233
- package/ts/proxies/smart-proxy/smart-proxy.ts +1 -2
- package/dist_ts/common/eventUtils.d.ts +0 -14
- package/dist_ts/common/eventUtils.js +0 -20
- package/dist_ts/common/types.d.ts +0 -82
- package/dist_ts/common/types.js +0 -15
- package/dist_ts/core/utils/event-system.d.ts +0 -200
- package/dist_ts/core/utils/event-system.js +0 -224
- package/dist_ts/core/utils/event-utils.d.ts +0 -15
- package/dist_ts/core/utils/event-utils.js +0 -11
- package/dist_ts/core/utils/route-manager.d.ts +0 -88
- package/dist_ts/core/utils/route-manager.js +0 -342
- package/dist_ts/core/utils/route-utils.d.ts +0 -28
- package/dist_ts/core/utils/route-utils.js +0 -67
- package/dist_ts/detection/detectors/http-detector-v2.d.ts +0 -33
- package/dist_ts/detection/detectors/http-detector-v2.js +0 -87
- package/dist_ts/detection/detectors/tls-detector-v2.d.ts +0 -33
- package/dist_ts/detection/detectors/tls-detector-v2.js +0 -80
- package/dist_ts/detection/protocol-detector-v2.d.ts +0 -46
- package/dist_ts/detection/protocol-detector-v2.js +0 -116
- package/dist_ts/forwarding/config/forwarding-types.d.ts +0 -42
- package/dist_ts/forwarding/config/forwarding-types.js +0 -18
- package/dist_ts/forwarding/config/index.d.ts +0 -9
- package/dist_ts/forwarding/config/index.js +0 -10
- package/dist_ts/forwarding/factory/forwarding-factory.d.ts +0 -25
- package/dist_ts/forwarding/factory/forwarding-factory.js +0 -172
- package/dist_ts/forwarding/factory/index.d.ts +0 -4
- package/dist_ts/forwarding/factory/index.js +0 -5
- package/dist_ts/forwarding/handlers/base-handler.d.ts +0 -62
- package/dist_ts/forwarding/handlers/base-handler.js +0 -121
- package/dist_ts/forwarding/handlers/http-handler.d.ts +0 -30
- package/dist_ts/forwarding/handlers/http-handler.js +0 -143
- package/dist_ts/forwarding/handlers/https-passthrough-handler.d.ts +0 -29
- package/dist_ts/forwarding/handlers/https-passthrough-handler.js +0 -156
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.d.ts +0 -36
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +0 -276
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.d.ts +0 -35
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +0 -261
- package/dist_ts/forwarding/handlers/index.d.ts +0 -8
- package/dist_ts/forwarding/handlers/index.js +0 -9
- package/dist_ts/forwarding/index.d.ts +0 -13
- package/dist_ts/forwarding/index.js +0 -16
- package/dist_ts/http/index.d.ts +0 -5
- package/dist_ts/http/index.js +0 -8
- package/dist_ts/http/models/http-types.d.ts +0 -6
- package/dist_ts/http/models/http-types.js +0 -7
- package/dist_ts/http/router/index.d.ts +0 -8
- package/dist_ts/http/router/index.js +0 -7
- package/dist_ts/http/router/proxy-router.d.ts +0 -115
- package/dist_ts/http/router/proxy-router.js +0 -325
- package/dist_ts/http/router/route-router.d.ts +0 -108
- package/dist_ts/http/router/route-router.js +0 -393
- package/dist_ts/protocols/tls/constants.d.ts +0 -122
- package/dist_ts/protocols/tls/constants.js +0 -135
- package/dist_ts/protocols/tls/parser.d.ts +0 -53
- package/dist_ts/protocols/tls/parser.js +0 -294
- package/dist_ts/protocols/tls/types.d.ts +0 -65
- package/dist_ts/protocols/tls/types.js +0 -5
- package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +0 -95
- package/dist_ts/proxies/http-proxy/certificate-manager.js +0 -214
- package/dist_ts/proxies/http-proxy/connection-pool.d.ts +0 -47
- package/dist_ts/proxies/http-proxy/connection-pool.js +0 -195
- package/dist_ts/proxies/http-proxy/context-creator.d.ts +0 -34
- package/dist_ts/proxies/http-proxy/context-creator.js +0 -108
- package/dist_ts/proxies/http-proxy/default-certificates.d.ts +0 -54
- package/dist_ts/proxies/http-proxy/default-certificates.js +0 -127
- package/dist_ts/proxies/http-proxy/function-cache.d.ts +0 -95
- package/dist_ts/proxies/http-proxy/function-cache.js +0 -215
- package/dist_ts/proxies/http-proxy/handlers/index.d.ts +0 -4
- package/dist_ts/proxies/http-proxy/handlers/index.js +0 -6
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.d.ts +0 -18
- package/dist_ts/proxies/http-proxy/handlers/redirect-handler.js +0 -78
- package/dist_ts/proxies/http-proxy/handlers/static-handler.d.ts +0 -19
- package/dist_ts/proxies/http-proxy/handlers/static-handler.js +0 -211
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -117
- package/dist_ts/proxies/http-proxy/http-proxy.js +0 -521
- package/dist_ts/proxies/http-proxy/http-request-handler.d.ts +0 -40
- package/dist_ts/proxies/http-proxy/http-request-handler.js +0 -257
- package/dist_ts/proxies/http-proxy/http2-request-handler.d.ts +0 -24
- package/dist_ts/proxies/http-proxy/http2-request-handler.js +0 -201
- package/dist_ts/proxies/http-proxy/index.d.ts +0 -14
- package/dist_ts/proxies/http-proxy/index.js +0 -16
- package/dist_ts/proxies/http-proxy/models/http-types.d.ts +0 -117
- package/dist_ts/proxies/http-proxy/models/http-types.js +0 -92
- package/dist_ts/proxies/http-proxy/models/index.d.ts +0 -5
- package/dist_ts/proxies/http-proxy/models/index.js +0 -6
- package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -75
- package/dist_ts/proxies/http-proxy/models/types.js +0 -35
- package/dist_ts/proxies/http-proxy/request-handler.d.ts +0 -97
- package/dist_ts/proxies/http-proxy/request-handler.js +0 -737
- package/dist_ts/proxies/http-proxy/security-manager.d.ts +0 -98
- package/dist_ts/proxies/http-proxy/security-manager.js +0 -341
- package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +0 -50
- package/dist_ts/proxies/http-proxy/websocket-handler.js +0 -505
- package/dist_ts/proxies/smart-proxy/acme-state-manager.d.ts +0 -42
- package/dist_ts/proxies/smart-proxy/acme-state-manager.js +0 -101
- package/dist_ts/proxies/smart-proxy/cert-store.d.ts +0 -10
- package/dist_ts/proxies/smart-proxy/cert-store.js +0 -72
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +0 -164
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +0 -745
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +0 -128
- package/dist_ts/proxies/smart-proxy/connection-manager.js +0 -689
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +0 -43
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +0 -180
- package/dist_ts/proxies/smart-proxy/metrics-collector.d.ts +0 -98
- package/dist_ts/proxies/smart-proxy/metrics-collector.js +0 -355
- package/dist_ts/proxies/smart-proxy/nftables-manager.d.ts +0 -82
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +0 -237
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +0 -117
- package/dist_ts/proxies/smart-proxy/port-manager.js +0 -318
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +0 -60
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +0 -1407
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +0 -112
- package/dist_ts/proxies/smart-proxy/route-manager.js +0 -453
- package/dist_ts/proxies/smart-proxy/route-orchestrator.d.ts +0 -56
- package/dist_ts/proxies/smart-proxy/route-orchestrator.js +0 -204
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +0 -23
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +0 -104
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +0 -74
- package/dist_ts/proxies/smart-proxy/security-manager.js +0 -227
- package/dist_ts/proxies/smart-proxy/throughput-tracker.d.ts +0 -36
- package/dist_ts/proxies/smart-proxy/throughput-tracker.js +0 -115
- package/dist_ts/proxies/smart-proxy/timeout-manager.d.ts +0 -48
- package/dist_ts/proxies/smart-proxy/timeout-manager.js +0 -158
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +0 -50
- package/dist_ts/proxies/smart-proxy/tls-manager.js +0 -110
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -161
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +0 -282
- package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +0 -73
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +0 -259
- package/dist_ts/routing/router/proxy-router.d.ts +0 -115
- package/dist_ts/routing/router/proxy-router.js +0 -325
- package/dist_ts/routing/router/route-router.d.ts +0 -108
- package/dist_ts/routing/router/route-router.js +0 -393
- package/dist_ts/tls/alerts/index.d.ts +0 -4
- package/dist_ts/tls/alerts/index.js +0 -5
- package/dist_ts/tls/alerts/tls-alert.d.ts +0 -150
- package/dist_ts/tls/alerts/tls-alert.js +0 -226
- package/dist_ts/tls/sni/client-hello-parser.d.ts +0 -100
- package/dist_ts/tls/sni/client-hello-parser.js +0 -464
- package/dist_ts/tls/sni/sni-extraction.d.ts +0 -58
- package/dist_ts/tls/sni/sni-extraction.js +0 -275
- package/dist_ts/tls/utils/index.d.ts +0 -4
- package/dist_ts/tls/utils/index.js +0 -5
- package/dist_ts/tls/utils/tls-utils.d.ts +0 -49
- package/dist_ts/tls/utils/tls-utils.js +0 -75
- package/ts/proxies/smart-proxy/rust-binary-locator.ts +0 -112
|
@@ -1,1407 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../../plugins.js';
|
|
2
|
-
import { logger } from '../../core/utils/logger.js';
|
|
3
|
-
import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
|
|
4
|
-
import { cleanupSocket, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
5
|
-
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
6
|
-
import { getUnderlyingSocket } from '../../core/models/socket-types.js';
|
|
7
|
-
import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
|
|
8
|
-
import { ProtocolDetector } from '../../detection/index.js';
|
|
9
|
-
/**
|
|
10
|
-
* Handles new connection processing and setup logic with support for route-based configuration
|
|
11
|
-
*/
|
|
12
|
-
export class RouteConnectionHandler {
|
|
13
|
-
constructor(smartProxy) {
|
|
14
|
-
this.smartProxy = smartProxy;
|
|
15
|
-
// Note: Route context caching was considered but not implemented
|
|
16
|
-
// as route contexts are lightweight and should be created fresh
|
|
17
|
-
// for each connection to ensure accurate context data
|
|
18
|
-
// RxJS Subject for new connections
|
|
19
|
-
this.newConnectionSubject = new plugins.smartrx.rxjs.Subject();
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Create a route context object for port and host mapping functions
|
|
23
|
-
*/
|
|
24
|
-
createRouteContext(options) {
|
|
25
|
-
return {
|
|
26
|
-
// Connection information
|
|
27
|
-
port: options.port,
|
|
28
|
-
domain: options.domain,
|
|
29
|
-
clientIp: options.clientIp,
|
|
30
|
-
serverIp: options.serverIp,
|
|
31
|
-
path: options.path,
|
|
32
|
-
query: options.query,
|
|
33
|
-
headers: options.headers,
|
|
34
|
-
// TLS information
|
|
35
|
-
isTls: options.isTls,
|
|
36
|
-
tlsVersion: options.tlsVersion,
|
|
37
|
-
// Route information
|
|
38
|
-
routeName: options.routeName,
|
|
39
|
-
routeId: options.routeId,
|
|
40
|
-
// Additional properties
|
|
41
|
-
timestamp: Date.now(),
|
|
42
|
-
connectionId: options.connectionId,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Determines if SNI is required for routing decisions on this port.
|
|
47
|
-
*
|
|
48
|
-
* SNI is REQUIRED when:
|
|
49
|
-
* - Multiple routes exist on this port (need SNI to pick correct route)
|
|
50
|
-
* - Route has dynamic target function (needs ctx.domain)
|
|
51
|
-
* - Route has specific domain restriction (strict validation)
|
|
52
|
-
*
|
|
53
|
-
* SNI is NOT required when:
|
|
54
|
-
* - TLS termination mode (HttpProxy handles session resumption)
|
|
55
|
-
* - Single route with static target and no domain restriction (or wildcard)
|
|
56
|
-
*/
|
|
57
|
-
calculateSniRequirement(port) {
|
|
58
|
-
const routesOnPort = this.smartProxy.routeManager.getRoutesForPort(port);
|
|
59
|
-
// No routes = no SNI requirement (will fail routing anyway)
|
|
60
|
-
if (routesOnPort.length === 0)
|
|
61
|
-
return false;
|
|
62
|
-
// Check if any route terminates TLS - if so, SNI not required
|
|
63
|
-
// (HttpProxy handles session resumption internally)
|
|
64
|
-
const hasTermination = routesOnPort.some(route => route.action.tls?.mode === 'terminate' ||
|
|
65
|
-
route.action.tls?.mode === 'terminate-and-reencrypt');
|
|
66
|
-
if (hasTermination)
|
|
67
|
-
return false;
|
|
68
|
-
// Multiple routes = need SNI to pick the correct route
|
|
69
|
-
if (routesOnPort.length > 1)
|
|
70
|
-
return true;
|
|
71
|
-
// Single route - check if it needs SNI for validation or routing
|
|
72
|
-
const route = routesOnPort[0];
|
|
73
|
-
// Dynamic host selection requires SNI (function receives ctx.domain)
|
|
74
|
-
const hasDynamicTarget = route.action.targets?.some(t => typeof t.host === 'function');
|
|
75
|
-
if (hasDynamicTarget)
|
|
76
|
-
return true;
|
|
77
|
-
// Specific domain restriction requires SNI for strict validation
|
|
78
|
-
const hasSpecificDomain = route.match.domains && !this.isWildcardOnly(route.match.domains);
|
|
79
|
-
if (hasSpecificDomain)
|
|
80
|
-
return true;
|
|
81
|
-
// Single route, static target(s), no domain restriction = SNI not required
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Check if domains config is wildcard-only (matches everything)
|
|
86
|
-
*/
|
|
87
|
-
isWildcardOnly(domains) {
|
|
88
|
-
const domainList = Array.isArray(domains) ? domains : [domains];
|
|
89
|
-
return domainList.length === 1 && domainList[0] === '*';
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Handle a new incoming connection
|
|
93
|
-
*/
|
|
94
|
-
handleConnection(socket) {
|
|
95
|
-
const remoteIP = socket.remoteAddress || '';
|
|
96
|
-
const localPort = socket.localPort || 0;
|
|
97
|
-
// Always wrap the socket to prepare for potential PROXY protocol
|
|
98
|
-
const wrappedSocket = new WrappedSocket(socket);
|
|
99
|
-
// If this is from a trusted proxy, log it
|
|
100
|
-
if (this.smartProxy.settings.proxyIPs?.includes(remoteIP)) {
|
|
101
|
-
logger.log('debug', `Connection from trusted proxy ${remoteIP}, PROXY protocol parsing will be enabled`, {
|
|
102
|
-
remoteIP,
|
|
103
|
-
component: 'route-handler'
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
// Generate connection ID first for atomic IP validation and tracking
|
|
107
|
-
const connectionId = this.smartProxy.connectionManager.generateConnectionId();
|
|
108
|
-
const clientIP = wrappedSocket.remoteAddress || '';
|
|
109
|
-
// Atomically validate IP and track the connection to prevent race conditions
|
|
110
|
-
// This ensures concurrent connections from the same IP are properly limited
|
|
111
|
-
const ipValidation = this.smartProxy.securityManager.validateAndTrackIP(clientIP, connectionId);
|
|
112
|
-
if (!ipValidation.allowed) {
|
|
113
|
-
connectionLogDeduplicator.log('ip-rejected', 'warn', `Connection rejected from ${clientIP}`, { remoteIP: clientIP, reason: ipValidation.reason, component: 'route-handler' }, clientIP);
|
|
114
|
-
cleanupSocket(wrappedSocket.socket, `rejected-${ipValidation.reason}`, { immediate: true });
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
// Create a new connection record with the wrapped socket
|
|
118
|
-
// Skip IP tracking since we already did it atomically above
|
|
119
|
-
const record = this.smartProxy.connectionManager.createConnection(wrappedSocket, {
|
|
120
|
-
connectionId,
|
|
121
|
-
skipIpTracking: true
|
|
122
|
-
});
|
|
123
|
-
if (!record) {
|
|
124
|
-
// Connection was rejected due to global limit - clean up the IP tracking we did
|
|
125
|
-
this.smartProxy.securityManager.removeConnectionByIP(clientIP, connectionId);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
// Emit new connection event
|
|
129
|
-
this.newConnectionSubject.next(record);
|
|
130
|
-
// Note: connectionId was already generated above for atomic IP tracking
|
|
131
|
-
// Apply socket optimizations (apply to underlying socket)
|
|
132
|
-
const underlyingSocket = wrappedSocket.socket;
|
|
133
|
-
underlyingSocket.setNoDelay(this.smartProxy.settings.noDelay);
|
|
134
|
-
// Apply keep-alive settings if enabled
|
|
135
|
-
if (this.smartProxy.settings.keepAlive) {
|
|
136
|
-
underlyingSocket.setKeepAlive(true, this.smartProxy.settings.keepAliveInitialDelay);
|
|
137
|
-
record.hasKeepAlive = true;
|
|
138
|
-
// Apply enhanced TCP keep-alive options if enabled
|
|
139
|
-
if (this.smartProxy.settings.enableKeepAliveProbes) {
|
|
140
|
-
try {
|
|
141
|
-
// These are platform-specific and may not be available
|
|
142
|
-
if ('setKeepAliveProbes' in underlyingSocket) {
|
|
143
|
-
underlyingSocket.setKeepAliveProbes(10);
|
|
144
|
-
}
|
|
145
|
-
if ('setKeepAliveInterval' in underlyingSocket) {
|
|
146
|
-
underlyingSocket.setKeepAliveInterval(1000);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
// Ignore errors - these are optional enhancements
|
|
151
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
152
|
-
logger.log('warn', `Enhanced TCP keep-alive settings not supported`, { connectionId, error: err, component: 'route-handler' });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
158
|
-
logger.log('info', `New connection from ${remoteIP} on port ${localPort}. ` +
|
|
159
|
-
`Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
|
|
160
|
-
`Active connections: ${this.smartProxy.connectionManager.getConnectionCount()}`, {
|
|
161
|
-
connectionId,
|
|
162
|
-
remoteIP,
|
|
163
|
-
localPort,
|
|
164
|
-
keepAlive: record.hasKeepAlive ? 'Enabled' : 'Disabled',
|
|
165
|
-
activeConnections: this.smartProxy.connectionManager.getConnectionCount(),
|
|
166
|
-
component: 'route-handler'
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
logger.log('info', `New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.smartProxy.connectionManager.getConnectionCount()}`, {
|
|
171
|
-
remoteIP,
|
|
172
|
-
localPort,
|
|
173
|
-
activeConnections: this.smartProxy.connectionManager.getConnectionCount(),
|
|
174
|
-
component: 'route-handler'
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
// Handle the connection - wait for initial data to determine if it's TLS
|
|
178
|
-
this.handleInitialData(wrappedSocket, record);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Handle initial data from a connection to determine routing
|
|
182
|
-
*/
|
|
183
|
-
handleInitialData(socket, record) {
|
|
184
|
-
const connectionId = record.id;
|
|
185
|
-
const localPort = record.localPort;
|
|
186
|
-
let initialDataReceived = false;
|
|
187
|
-
// Check if any routes on this port require TLS handling
|
|
188
|
-
const allRoutes = this.smartProxy.routeManager.getRoutes();
|
|
189
|
-
const needsTlsHandling = allRoutes.some(route => {
|
|
190
|
-
// Check if route matches this port
|
|
191
|
-
const matchesPort = this.smartProxy.routeManager.getRoutesForPort(localPort).includes(route);
|
|
192
|
-
return matchesPort &&
|
|
193
|
-
route.action.type === 'forward' &&
|
|
194
|
-
route.action.tls &&
|
|
195
|
-
(route.action.tls.mode === 'terminate' ||
|
|
196
|
-
route.action.tls.mode === 'passthrough');
|
|
197
|
-
});
|
|
198
|
-
// Smart SNI requirement calculation
|
|
199
|
-
// Determines if we need SNI for routing decisions on this port
|
|
200
|
-
const needsSniForRouting = this.calculateSniRequirement(localPort);
|
|
201
|
-
const allowSessionTicket = !needsSniForRouting;
|
|
202
|
-
// If no routes require TLS handling and it's not port 443, route immediately
|
|
203
|
-
if (!needsTlsHandling && localPort !== 443) {
|
|
204
|
-
// Extract underlying socket for socket-utils functions
|
|
205
|
-
const underlyingSocket = getUnderlyingSocket(socket);
|
|
206
|
-
// Set up proper socket handlers for immediate routing
|
|
207
|
-
setupSocketHandlers(underlyingSocket, (reason) => {
|
|
208
|
-
// Always cleanup when incoming socket closes
|
|
209
|
-
// This prevents connection accumulation in proxy chains
|
|
210
|
-
logger.log('debug', `Connection ${connectionId} closed during immediate routing: ${reason}`, {
|
|
211
|
-
connectionId,
|
|
212
|
-
remoteIP: record.remoteIP,
|
|
213
|
-
reason,
|
|
214
|
-
hasOutgoing: !!record.outgoing,
|
|
215
|
-
outgoingState: record.outgoing?.readyState,
|
|
216
|
-
component: 'route-handler'
|
|
217
|
-
});
|
|
218
|
-
// If there's a pending or established outgoing connection, destroy it
|
|
219
|
-
if (record.outgoing && !record.outgoing.destroyed) {
|
|
220
|
-
logger.log('debug', `Destroying outgoing connection for ${connectionId}`, {
|
|
221
|
-
connectionId,
|
|
222
|
-
outgoingState: record.outgoing.readyState,
|
|
223
|
-
component: 'route-handler'
|
|
224
|
-
});
|
|
225
|
-
record.outgoing.destroy();
|
|
226
|
-
}
|
|
227
|
-
// Always cleanup the connection record
|
|
228
|
-
this.smartProxy.connectionManager.cleanupConnection(record, reason);
|
|
229
|
-
}, undefined, // Use default timeout handler
|
|
230
|
-
'immediate-route-client');
|
|
231
|
-
// Route immediately for non-TLS connections
|
|
232
|
-
this.routeConnection(socket, record, '', undefined);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
// Otherwise, wait for initial data to check if it's TLS
|
|
236
|
-
// Set an initial timeout for handshake data
|
|
237
|
-
let initialTimeout = setTimeout(() => {
|
|
238
|
-
if (!initialDataReceived) {
|
|
239
|
-
logger.log('warn', `No initial data received from ${record.remoteIP} after ${this.smartProxy.settings.initialDataTimeout}ms for connection ${connectionId}`, {
|
|
240
|
-
connectionId,
|
|
241
|
-
timeout: this.smartProxy.settings.initialDataTimeout,
|
|
242
|
-
remoteIP: record.remoteIP,
|
|
243
|
-
component: 'route-handler'
|
|
244
|
-
});
|
|
245
|
-
// Add a grace period
|
|
246
|
-
setTimeout(() => {
|
|
247
|
-
if (!initialDataReceived) {
|
|
248
|
-
logger.log('warn', `Final initial data timeout after grace period for connection ${connectionId}`, {
|
|
249
|
-
connectionId,
|
|
250
|
-
component: 'route-handler'
|
|
251
|
-
});
|
|
252
|
-
if (record.incomingTerminationReason === null) {
|
|
253
|
-
record.incomingTerminationReason = 'initial_timeout';
|
|
254
|
-
this.smartProxy.connectionManager.incrementTerminationStat('incoming', 'initial_timeout');
|
|
255
|
-
}
|
|
256
|
-
socket.end();
|
|
257
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'initial_timeout');
|
|
258
|
-
}
|
|
259
|
-
}, 30000);
|
|
260
|
-
}
|
|
261
|
-
}, this.smartProxy.settings.initialDataTimeout);
|
|
262
|
-
// Make sure timeout doesn't keep the process alive
|
|
263
|
-
if (initialTimeout.unref) {
|
|
264
|
-
initialTimeout.unref();
|
|
265
|
-
}
|
|
266
|
-
// Set up error handler
|
|
267
|
-
socket.on('error', this.smartProxy.connectionManager.handleError('incoming', record));
|
|
268
|
-
// Add close/end handlers to catch immediate disconnections
|
|
269
|
-
socket.once('close', () => {
|
|
270
|
-
if (!initialDataReceived) {
|
|
271
|
-
logger.log('warn', `Connection ${connectionId} closed before sending initial data`, {
|
|
272
|
-
connectionId,
|
|
273
|
-
remoteIP: record.remoteIP,
|
|
274
|
-
component: 'route-handler'
|
|
275
|
-
});
|
|
276
|
-
if (initialTimeout) {
|
|
277
|
-
clearTimeout(initialTimeout);
|
|
278
|
-
initialTimeout = null;
|
|
279
|
-
}
|
|
280
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'closed_before_data');
|
|
281
|
-
}
|
|
282
|
-
});
|
|
283
|
-
socket.once('end', () => {
|
|
284
|
-
if (!initialDataReceived) {
|
|
285
|
-
logger.log('debug', `Connection ${connectionId} ended before sending initial data`, {
|
|
286
|
-
connectionId,
|
|
287
|
-
remoteIP: record.remoteIP,
|
|
288
|
-
component: 'route-handler'
|
|
289
|
-
});
|
|
290
|
-
if (initialTimeout) {
|
|
291
|
-
clearTimeout(initialTimeout);
|
|
292
|
-
initialTimeout = null;
|
|
293
|
-
}
|
|
294
|
-
// Don't cleanup on 'end' - wait for 'close'
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
// Handler for processing initial data (after potential PROXY protocol)
|
|
298
|
-
const processInitialData = async (chunk) => {
|
|
299
|
-
// Create connection context for protocol detection
|
|
300
|
-
const context = ProtocolDetector.createConnectionContext({
|
|
301
|
-
sourceIp: record.remoteIP,
|
|
302
|
-
sourcePort: socket.remotePort || 0,
|
|
303
|
-
destIp: socket.localAddress || '',
|
|
304
|
-
destPort: socket.localPort || 0,
|
|
305
|
-
socketId: record.id
|
|
306
|
-
});
|
|
307
|
-
const detectionResult = await ProtocolDetector.detectWithContext(chunk, context, { extractFullHeaders: false } // Only extract essential info for routing
|
|
308
|
-
);
|
|
309
|
-
// Block non-TLS connections on port 443
|
|
310
|
-
if (localPort === 443 && detectionResult.protocol !== 'tls') {
|
|
311
|
-
logger.log('warn', `Non-TLS connection ${record.id} detected on port 443. Terminating connection - only TLS traffic is allowed on standard HTTPS port.`, {
|
|
312
|
-
connectionId: record.id,
|
|
313
|
-
detectedProtocol: detectionResult.protocol,
|
|
314
|
-
message: 'Terminating connection - only TLS traffic is allowed on standard HTTPS port.',
|
|
315
|
-
component: 'route-handler'
|
|
316
|
-
});
|
|
317
|
-
if (record.incomingTerminationReason === null) {
|
|
318
|
-
record.incomingTerminationReason = 'non_tls_blocked';
|
|
319
|
-
this.smartProxy.connectionManager.incrementTerminationStat('incoming', 'non_tls_blocked');
|
|
320
|
-
}
|
|
321
|
-
socket.end();
|
|
322
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'non_tls_blocked');
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
// Extract domain and protocol info
|
|
326
|
-
let serverName = '';
|
|
327
|
-
if (detectionResult.protocol === 'tls') {
|
|
328
|
-
record.isTLS = true;
|
|
329
|
-
serverName = detectionResult.connectionInfo.domain || '';
|
|
330
|
-
// Lock the connection to the negotiated SNI
|
|
331
|
-
record.lockedDomain = serverName;
|
|
332
|
-
// Check if we should reject connections without SNI
|
|
333
|
-
if (!serverName && allowSessionTicket === false) {
|
|
334
|
-
logger.log('warn', `No SNI detected in TLS ClientHello for connection ${record.id}; sending TLS alert`, {
|
|
335
|
-
connectionId: record.id,
|
|
336
|
-
component: 'route-handler'
|
|
337
|
-
});
|
|
338
|
-
if (record.incomingTerminationReason === null) {
|
|
339
|
-
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
|
|
340
|
-
this.smartProxy.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
|
|
341
|
-
}
|
|
342
|
-
const alert = Buffer.from([0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x70]);
|
|
343
|
-
try {
|
|
344
|
-
// Count the alert bytes being sent
|
|
345
|
-
record.bytesSent += alert.length;
|
|
346
|
-
if (this.smartProxy.metricsCollector) {
|
|
347
|
-
this.smartProxy.metricsCollector.recordBytes(record.id, 0, alert.length);
|
|
348
|
-
}
|
|
349
|
-
socket.cork();
|
|
350
|
-
socket.write(alert);
|
|
351
|
-
socket.uncork();
|
|
352
|
-
socket.end();
|
|
353
|
-
}
|
|
354
|
-
catch {
|
|
355
|
-
socket.end();
|
|
356
|
-
}
|
|
357
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
361
|
-
logger.log('info', `TLS connection with SNI`, {
|
|
362
|
-
connectionId: record.id,
|
|
363
|
-
serverName: serverName || '(empty)',
|
|
364
|
-
component: 'route-handler'
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
else if (detectionResult.protocol === 'http') {
|
|
369
|
-
// For HTTP, extract domain from Host header
|
|
370
|
-
serverName = detectionResult.connectionInfo.domain || '';
|
|
371
|
-
// Store HTTP-specific info for later use
|
|
372
|
-
record.httpInfo = {
|
|
373
|
-
method: detectionResult.connectionInfo.method,
|
|
374
|
-
path: detectionResult.connectionInfo.path,
|
|
375
|
-
headers: detectionResult.connectionInfo.headers
|
|
376
|
-
};
|
|
377
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
378
|
-
logger.log('info', `HTTP connection detected`, {
|
|
379
|
-
connectionId: record.id,
|
|
380
|
-
domain: serverName || '(no host header)',
|
|
381
|
-
method: detectionResult.connectionInfo.method,
|
|
382
|
-
path: detectionResult.connectionInfo.path,
|
|
383
|
-
component: 'route-handler'
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
// Find the appropriate route for this connection
|
|
388
|
-
this.routeConnection(socket, record, serverName, chunk, detectionResult);
|
|
389
|
-
};
|
|
390
|
-
// First data handler to capture initial TLS handshake or PROXY protocol
|
|
391
|
-
socket.once('data', async (chunk) => {
|
|
392
|
-
// Clear the initial timeout since we've received data
|
|
393
|
-
if (initialTimeout) {
|
|
394
|
-
clearTimeout(initialTimeout);
|
|
395
|
-
initialTimeout = null;
|
|
396
|
-
}
|
|
397
|
-
initialDataReceived = true;
|
|
398
|
-
record.hasReceivedInitialData = true;
|
|
399
|
-
// Check if this is from a trusted proxy and might have PROXY protocol
|
|
400
|
-
if (this.smartProxy.settings.proxyIPs?.includes(socket.remoteAddress || '') && this.smartProxy.settings.acceptProxyProtocol !== false) {
|
|
401
|
-
// Check if this starts with PROXY protocol
|
|
402
|
-
if (chunk.toString('ascii', 0, Math.min(6, chunk.length)).startsWith('PROXY ')) {
|
|
403
|
-
try {
|
|
404
|
-
const parseResult = ProxyProtocolParser.parse(chunk);
|
|
405
|
-
if (parseResult.proxyInfo) {
|
|
406
|
-
// Update the wrapped socket with real client info (if it's a WrappedSocket)
|
|
407
|
-
if (socket instanceof WrappedSocket) {
|
|
408
|
-
socket.setProxyInfo(parseResult.proxyInfo.sourceIP, parseResult.proxyInfo.sourcePort);
|
|
409
|
-
}
|
|
410
|
-
// Update connection record with real client info
|
|
411
|
-
record.remoteIP = parseResult.proxyInfo.sourceIP;
|
|
412
|
-
record.remotePort = parseResult.proxyInfo.sourcePort;
|
|
413
|
-
logger.log('info', `PROXY protocol parsed successfully`, {
|
|
414
|
-
connectionId,
|
|
415
|
-
realClientIP: parseResult.proxyInfo.sourceIP,
|
|
416
|
-
realClientPort: parseResult.proxyInfo.sourcePort,
|
|
417
|
-
proxyIP: socket.remoteAddress,
|
|
418
|
-
component: 'route-handler'
|
|
419
|
-
});
|
|
420
|
-
// Process remaining data if any
|
|
421
|
-
if (parseResult.remainingData.length > 0) {
|
|
422
|
-
processInitialData(parseResult.remainingData);
|
|
423
|
-
}
|
|
424
|
-
else {
|
|
425
|
-
// Wait for more data
|
|
426
|
-
socket.once('data', processInitialData);
|
|
427
|
-
}
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
catch (error) {
|
|
432
|
-
logger.log('error', `Failed to parse PROXY protocol from trusted proxy`, {
|
|
433
|
-
connectionId,
|
|
434
|
-
error: error.message,
|
|
435
|
-
proxyIP: socket.remoteAddress,
|
|
436
|
-
component: 'route-handler'
|
|
437
|
-
});
|
|
438
|
-
// Continue processing as normal data
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
// Process as normal data (no PROXY protocol)
|
|
443
|
-
processInitialData(chunk);
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
/**
|
|
447
|
-
* Route the connection based on match criteria
|
|
448
|
-
*/
|
|
449
|
-
routeConnection(socket, record, serverName, initialChunk, detectionResult // Using any temporarily to avoid circular dependency issues
|
|
450
|
-
) {
|
|
451
|
-
const connectionId = record.id;
|
|
452
|
-
const localPort = record.localPort;
|
|
453
|
-
const remoteIP = record.remoteIP;
|
|
454
|
-
// Check if this is an HTTP proxy port
|
|
455
|
-
const isHttpProxyPort = this.smartProxy.settings.useHttpProxy?.includes(localPort);
|
|
456
|
-
// For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
|
|
457
|
-
const skipDomainCheck = isHttpProxyPort && !record.isTLS;
|
|
458
|
-
// Create route context for matching
|
|
459
|
-
const routeContext = {
|
|
460
|
-
port: localPort,
|
|
461
|
-
domain: skipDomainCheck ? undefined : serverName, // Skip domain if HTTP proxy without TLS
|
|
462
|
-
clientIp: remoteIP,
|
|
463
|
-
serverIp: socket.localAddress || '',
|
|
464
|
-
path: undefined, // We don't have path info at this point
|
|
465
|
-
isTls: record.isTLS,
|
|
466
|
-
tlsVersion: undefined, // We don't extract TLS version yet
|
|
467
|
-
timestamp: Date.now(),
|
|
468
|
-
connectionId: record.id
|
|
469
|
-
};
|
|
470
|
-
// Find matching route
|
|
471
|
-
const routeMatch = this.smartProxy.routeManager.findMatchingRoute(routeContext);
|
|
472
|
-
if (!routeMatch) {
|
|
473
|
-
logger.log('warn', `No route found for ${serverName || 'connection'} on port ${localPort} (connection: ${connectionId})`, {
|
|
474
|
-
connectionId,
|
|
475
|
-
serverName: serverName || 'connection',
|
|
476
|
-
localPort,
|
|
477
|
-
component: 'route-handler'
|
|
478
|
-
});
|
|
479
|
-
// No matching route, use default/fallback handling
|
|
480
|
-
logger.log('info', `Using default route handling for connection ${connectionId}`, {
|
|
481
|
-
connectionId,
|
|
482
|
-
component: 'route-handler'
|
|
483
|
-
});
|
|
484
|
-
// Check default security settings
|
|
485
|
-
const defaultSecuritySettings = this.smartProxy.settings.defaults?.security;
|
|
486
|
-
if (defaultSecuritySettings) {
|
|
487
|
-
if (defaultSecuritySettings.ipAllowList && defaultSecuritySettings.ipAllowList.length > 0) {
|
|
488
|
-
const isAllowed = this.smartProxy.securityManager.isIPAuthorized(remoteIP, defaultSecuritySettings.ipAllowList, defaultSecuritySettings.ipBlockList || []);
|
|
489
|
-
if (!isAllowed) {
|
|
490
|
-
logger.log('warn', `IP ${remoteIP} not in default allowed list for connection ${connectionId}`, {
|
|
491
|
-
connectionId,
|
|
492
|
-
remoteIP,
|
|
493
|
-
component: 'route-handler'
|
|
494
|
-
});
|
|
495
|
-
socket.end();
|
|
496
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'ip_blocked');
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
// Setup direct connection with default settings
|
|
502
|
-
if (this.smartProxy.settings.defaults?.target) {
|
|
503
|
-
// Use defaults from configuration
|
|
504
|
-
const targetHost = this.smartProxy.settings.defaults.target.host;
|
|
505
|
-
const targetPort = this.smartProxy.settings.defaults.target.port;
|
|
506
|
-
return this.setupDirectConnection(socket, record, serverName, initialChunk, undefined, targetHost, targetPort);
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
// No default target available, terminate the connection
|
|
510
|
-
logger.log('warn', `No default target configured for connection ${connectionId}. Closing connection`, {
|
|
511
|
-
connectionId,
|
|
512
|
-
component: 'route-handler'
|
|
513
|
-
});
|
|
514
|
-
socket.end();
|
|
515
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'no_default_target');
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
// A matching route was found
|
|
520
|
-
const route = routeMatch.route;
|
|
521
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
522
|
-
logger.log('info', `Route matched`, {
|
|
523
|
-
connectionId,
|
|
524
|
-
routeName: route.name || 'unnamed',
|
|
525
|
-
serverName: serverName || 'connection',
|
|
526
|
-
localPort,
|
|
527
|
-
component: 'route-handler'
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
// Apply route-specific security checks
|
|
531
|
-
if (route.security) {
|
|
532
|
-
// Check IP allow/block lists
|
|
533
|
-
if (route.security.ipAllowList || route.security.ipBlockList) {
|
|
534
|
-
const isIPAllowed = this.smartProxy.securityManager.isIPAuthorized(remoteIP, route.security.ipAllowList || [], route.security.ipBlockList || []);
|
|
535
|
-
if (!isIPAllowed) {
|
|
536
|
-
// Deduplicated logging for route IP blocks
|
|
537
|
-
connectionLogDeduplicator.log('ip-rejected', 'warn', `IP blocked by route security`, {
|
|
538
|
-
connectionId,
|
|
539
|
-
remoteIP,
|
|
540
|
-
routeName: route.name || 'unnamed',
|
|
541
|
-
reason: 'route-ip-blocked',
|
|
542
|
-
component: 'route-handler'
|
|
543
|
-
}, remoteIP);
|
|
544
|
-
socket.end();
|
|
545
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'route_ip_blocked');
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
// Check max connections per route
|
|
550
|
-
if (route.security.maxConnections !== undefined) {
|
|
551
|
-
const routeId = route.id || route.name || 'unnamed';
|
|
552
|
-
const currentConnections = this.smartProxy.connectionManager.getConnectionCountByRoute(routeId);
|
|
553
|
-
if (currentConnections >= route.security.maxConnections) {
|
|
554
|
-
// Deduplicated logging for route connection limits
|
|
555
|
-
connectionLogDeduplicator.log('connection-rejected', 'warn', `Route connection limit reached`, {
|
|
556
|
-
connectionId,
|
|
557
|
-
routeName: route.name,
|
|
558
|
-
currentConnections,
|
|
559
|
-
maxConnections: route.security.maxConnections,
|
|
560
|
-
reason: 'route-limit',
|
|
561
|
-
component: 'route-handler'
|
|
562
|
-
}, `route-limit-${route.name}`);
|
|
563
|
-
socket.end();
|
|
564
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'route_connection_limit');
|
|
565
|
-
return;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
// Check authentication requirements
|
|
569
|
-
if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
|
|
570
|
-
// Authentication checks would typically happen at the HTTP layer
|
|
571
|
-
// For non-HTTP connections or passthrough, we can't enforce authentication
|
|
572
|
-
if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
|
|
573
|
-
logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
|
|
574
|
-
connectionId,
|
|
575
|
-
routeName: route.name,
|
|
576
|
-
tlsMode: route.action.tls?.mode || 'none',
|
|
577
|
-
component: 'route-handler'
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
// Handle the route based on its action type
|
|
583
|
-
switch (route.action.type) {
|
|
584
|
-
case 'forward':
|
|
585
|
-
return this.handleForwardAction(socket, record, route, initialChunk, detectionResult);
|
|
586
|
-
case 'socket-handler':
|
|
587
|
-
logger.log('info', `Handling socket-handler action for route ${route.name}`, {
|
|
588
|
-
connectionId,
|
|
589
|
-
routeName: route.name,
|
|
590
|
-
component: 'route-handler'
|
|
591
|
-
});
|
|
592
|
-
this.handleSocketHandlerAction(socket, record, route, initialChunk);
|
|
593
|
-
return;
|
|
594
|
-
default:
|
|
595
|
-
logger.log('error', `Unknown action type '${route.action.type}' for connection ${connectionId}`, {
|
|
596
|
-
connectionId,
|
|
597
|
-
actionType: route.action.type,
|
|
598
|
-
component: 'route-handler'
|
|
599
|
-
});
|
|
600
|
-
socket.end();
|
|
601
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'unknown_action');
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Select the appropriate target from the targets array based on sub-matching criteria
|
|
606
|
-
*/
|
|
607
|
-
selectTarget(targets, context) {
|
|
608
|
-
// Sort targets by priority (higher first)
|
|
609
|
-
const sortedTargets = [...targets].sort((a, b) => (b.priority || 0) - (a.priority || 0));
|
|
610
|
-
// Find the first matching target
|
|
611
|
-
for (const target of sortedTargets) {
|
|
612
|
-
if (!target.match) {
|
|
613
|
-
// No match criteria means this is a default/fallback target
|
|
614
|
-
return target;
|
|
615
|
-
}
|
|
616
|
-
// Check port match
|
|
617
|
-
if (target.match.ports && !target.match.ports.includes(context.port)) {
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
// Check path match (supports wildcards)
|
|
621
|
-
if (target.match.path && context.path) {
|
|
622
|
-
const pathPattern = target.match.path.replace(/\*/g, '.*');
|
|
623
|
-
const pathRegex = new RegExp(`^${pathPattern}$`);
|
|
624
|
-
if (!pathRegex.test(context.path)) {
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
// Check method match
|
|
629
|
-
if (target.match.method && context.method && !target.match.method.includes(context.method)) {
|
|
630
|
-
continue;
|
|
631
|
-
}
|
|
632
|
-
// Check headers match
|
|
633
|
-
if (target.match.headers && context.headers) {
|
|
634
|
-
let headersMatch = true;
|
|
635
|
-
for (const [key, pattern] of Object.entries(target.match.headers)) {
|
|
636
|
-
const headerValue = context.headers[key.toLowerCase()];
|
|
637
|
-
if (!headerValue) {
|
|
638
|
-
headersMatch = false;
|
|
639
|
-
break;
|
|
640
|
-
}
|
|
641
|
-
if (pattern instanceof RegExp) {
|
|
642
|
-
if (!pattern.test(headerValue)) {
|
|
643
|
-
headersMatch = false;
|
|
644
|
-
break;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
else if (headerValue !== pattern) {
|
|
648
|
-
headersMatch = false;
|
|
649
|
-
break;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (!headersMatch) {
|
|
653
|
-
continue;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
// All criteria matched
|
|
657
|
-
return target;
|
|
658
|
-
}
|
|
659
|
-
// No matching target found, return the first target without match criteria (default)
|
|
660
|
-
return sortedTargets.find(t => !t.match) || null;
|
|
661
|
-
}
|
|
662
|
-
/**
|
|
663
|
-
* Handle a forward action for a route
|
|
664
|
-
*/
|
|
665
|
-
handleForwardAction(socket, record, route, initialChunk, detectionResult // Using any temporarily to avoid circular dependency issues
|
|
666
|
-
) {
|
|
667
|
-
const connectionId = record.id;
|
|
668
|
-
const action = route.action;
|
|
669
|
-
// Store the route config in the connection record for metrics and other uses
|
|
670
|
-
record.routeConfig = route;
|
|
671
|
-
record.routeId = route.id || route.name || 'unnamed';
|
|
672
|
-
// Track connection by route
|
|
673
|
-
this.smartProxy.connectionManager.trackConnectionByRoute(record.routeId, record.id);
|
|
674
|
-
// Check if this route uses NFTables for forwarding
|
|
675
|
-
if (action.forwardingEngine === 'nftables') {
|
|
676
|
-
// NFTables handles packet forwarding at the kernel level
|
|
677
|
-
// The application should NOT interfere with these connections
|
|
678
|
-
// Log the connection for monitoring purposes
|
|
679
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
680
|
-
logger.log('info', `NFTables forwarding (kernel-level)`, {
|
|
681
|
-
connectionId: record.id,
|
|
682
|
-
source: `${record.remoteIP}:${socket.remotePort}`,
|
|
683
|
-
destination: `${socket.localAddress}:${record.localPort}`,
|
|
684
|
-
routeName: route.name || 'unnamed',
|
|
685
|
-
domain: record.lockedDomain || 'n/a',
|
|
686
|
-
component: 'route-handler'
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
else {
|
|
690
|
-
logger.log('info', `NFTables forwarding`, {
|
|
691
|
-
connectionId: record.id,
|
|
692
|
-
remoteIP: record.remoteIP,
|
|
693
|
-
localPort: record.localPort,
|
|
694
|
-
routeName: route.name || 'unnamed',
|
|
695
|
-
component: 'route-handler'
|
|
696
|
-
});
|
|
697
|
-
}
|
|
698
|
-
// Additional NFTables-specific logging if configured
|
|
699
|
-
if (action.nftables) {
|
|
700
|
-
const nftConfig = action.nftables;
|
|
701
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
702
|
-
logger.log('info', `NFTables config`, {
|
|
703
|
-
connectionId: record.id,
|
|
704
|
-
protocol: nftConfig.protocol || 'tcp',
|
|
705
|
-
preserveSourceIP: nftConfig.preserveSourceIP || false,
|
|
706
|
-
priority: nftConfig.priority || 'default',
|
|
707
|
-
maxRate: nftConfig.maxRate || 'unlimited',
|
|
708
|
-
component: 'route-handler'
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
// For NFTables routes, we should still track the connection but not interfere
|
|
713
|
-
// Mark the connection as using network proxy so it's cleaned up properly
|
|
714
|
-
record.usingNetworkProxy = true;
|
|
715
|
-
// We don't close the socket - just let it remain open
|
|
716
|
-
// The kernel-level NFTables rules will handle the actual forwarding
|
|
717
|
-
// Set up cleanup when the socket eventually closes
|
|
718
|
-
socket.once('close', () => {
|
|
719
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'nftables_closed');
|
|
720
|
-
});
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
// Select the appropriate target from the targets array
|
|
724
|
-
if (!action.targets || action.targets.length === 0) {
|
|
725
|
-
logger.log('error', `Forward action missing targets configuration for connection ${connectionId}`, {
|
|
726
|
-
connectionId,
|
|
727
|
-
component: 'route-handler'
|
|
728
|
-
});
|
|
729
|
-
socket.end();
|
|
730
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'missing_targets');
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
// Create context for target selection
|
|
734
|
-
const targetSelectionContext = {
|
|
735
|
-
port: record.localPort,
|
|
736
|
-
path: record.httpInfo?.path,
|
|
737
|
-
headers: record.httpInfo?.headers,
|
|
738
|
-
method: record.httpInfo?.method
|
|
739
|
-
};
|
|
740
|
-
const selectedTarget = this.selectTarget(action.targets, targetSelectionContext);
|
|
741
|
-
if (!selectedTarget) {
|
|
742
|
-
logger.log('error', `No matching target found for connection ${connectionId}`, {
|
|
743
|
-
connectionId,
|
|
744
|
-
port: targetSelectionContext.port,
|
|
745
|
-
component: 'route-handler'
|
|
746
|
-
});
|
|
747
|
-
socket.end();
|
|
748
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'no_matching_target');
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
// Create the routing context for this connection
|
|
752
|
-
const routeContext = this.createRouteContext({
|
|
753
|
-
connectionId: record.id,
|
|
754
|
-
port: record.localPort,
|
|
755
|
-
domain: record.lockedDomain,
|
|
756
|
-
clientIp: record.remoteIP,
|
|
757
|
-
serverIp: socket.localAddress || '',
|
|
758
|
-
isTls: record.isTLS || false,
|
|
759
|
-
tlsVersion: record.tlsVersion,
|
|
760
|
-
routeName: route.name,
|
|
761
|
-
routeId: route.id,
|
|
762
|
-
});
|
|
763
|
-
// Note: Route contexts are not cached to ensure fresh data for each connection
|
|
764
|
-
// Determine host using function or static value
|
|
765
|
-
let targetHost;
|
|
766
|
-
if (typeof selectedTarget.host === 'function') {
|
|
767
|
-
try {
|
|
768
|
-
targetHost = selectedTarget.host(routeContext);
|
|
769
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
770
|
-
logger.log('info', `Dynamic host resolved to ${Array.isArray(targetHost) ? targetHost.join(', ') : targetHost} for connection ${connectionId}`, {
|
|
771
|
-
connectionId,
|
|
772
|
-
targetHost: Array.isArray(targetHost) ? targetHost.join(', ') : targetHost,
|
|
773
|
-
component: 'route-handler'
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
catch (err) {
|
|
778
|
-
logger.log('error', `Error in host mapping function for connection ${connectionId}: ${err}`, {
|
|
779
|
-
connectionId,
|
|
780
|
-
error: err,
|
|
781
|
-
component: 'route-handler'
|
|
782
|
-
});
|
|
783
|
-
socket.end();
|
|
784
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'host_mapping_error');
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
else {
|
|
789
|
-
targetHost = selectedTarget.host;
|
|
790
|
-
}
|
|
791
|
-
// If an array of hosts, select one randomly for load balancing
|
|
792
|
-
const selectedHost = Array.isArray(targetHost)
|
|
793
|
-
? targetHost[Math.floor(Math.random() * targetHost.length)]
|
|
794
|
-
: targetHost;
|
|
795
|
-
// Determine port using function or static value
|
|
796
|
-
let targetPort;
|
|
797
|
-
if (typeof selectedTarget.port === 'function') {
|
|
798
|
-
try {
|
|
799
|
-
targetPort = selectedTarget.port(routeContext);
|
|
800
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
801
|
-
logger.log('info', `Dynamic port mapping from ${record.localPort} to ${targetPort} for connection ${connectionId}`, {
|
|
802
|
-
connectionId,
|
|
803
|
-
sourcePort: record.localPort,
|
|
804
|
-
targetPort,
|
|
805
|
-
component: 'route-handler'
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
// Store the resolved target port in the context for potential future use
|
|
809
|
-
routeContext.targetPort = targetPort;
|
|
810
|
-
}
|
|
811
|
-
catch (err) {
|
|
812
|
-
logger.log('error', `Error in port mapping function for connection ${connectionId}: ${err}`, {
|
|
813
|
-
connectionId,
|
|
814
|
-
error: err,
|
|
815
|
-
component: 'route-handler'
|
|
816
|
-
});
|
|
817
|
-
socket.end();
|
|
818
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'port_mapping_error');
|
|
819
|
-
return;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
else if (selectedTarget.port === 'preserve') {
|
|
823
|
-
// Use incoming port if port is 'preserve'
|
|
824
|
-
targetPort = record.localPort;
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
// Use static port from configuration
|
|
828
|
-
targetPort = selectedTarget.port;
|
|
829
|
-
}
|
|
830
|
-
// Store the resolved host in the context
|
|
831
|
-
routeContext.targetHost = selectedHost;
|
|
832
|
-
// Get effective settings (target overrides route-level settings)
|
|
833
|
-
const effectiveTls = selectedTarget.tls || action.tls;
|
|
834
|
-
const effectiveWebsocket = selectedTarget.websocket || action.websocket;
|
|
835
|
-
const effectiveSendProxyProtocol = selectedTarget.sendProxyProtocol !== undefined
|
|
836
|
-
? selectedTarget.sendProxyProtocol
|
|
837
|
-
: action.sendProxyProtocol;
|
|
838
|
-
// Determine if this needs TLS handling
|
|
839
|
-
if (effectiveTls) {
|
|
840
|
-
switch (effectiveTls.mode) {
|
|
841
|
-
case 'passthrough':
|
|
842
|
-
// For TLS passthrough, just forward directly
|
|
843
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
844
|
-
logger.log('info', `Using TLS passthrough to ${selectedHost}:${targetPort} for connection ${connectionId}`, {
|
|
845
|
-
connectionId,
|
|
846
|
-
targetHost: selectedHost,
|
|
847
|
-
targetPort,
|
|
848
|
-
component: 'route-handler'
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
return this.setupDirectConnection(socket, record, record.lockedDomain, initialChunk, undefined, selectedHost, targetPort);
|
|
852
|
-
case 'terminate':
|
|
853
|
-
case 'terminate-and-reencrypt':
|
|
854
|
-
// For TLS termination, use HttpProxy
|
|
855
|
-
if (this.smartProxy.httpProxyBridge.getHttpProxy()) {
|
|
856
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
857
|
-
logger.log('info', `Using HttpProxy for TLS termination to ${Array.isArray(selectedTarget.host) ? selectedTarget.host.join(', ') : selectedTarget.host} for connection ${connectionId}`, {
|
|
858
|
-
connectionId,
|
|
859
|
-
targetHost: selectedTarget.host,
|
|
860
|
-
component: 'route-handler'
|
|
861
|
-
});
|
|
862
|
-
}
|
|
863
|
-
// If we have an initial chunk with TLS data, start processing it
|
|
864
|
-
if (initialChunk && record.isTLS) {
|
|
865
|
-
this.smartProxy.httpProxyBridge.forwardToHttpProxy(connectionId, socket, record, initialChunk, this.smartProxy.settings.httpProxyPort || 8443, (reason) => this.smartProxy.connectionManager.cleanupConnection(record, reason));
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
// This shouldn't normally happen - we should have TLS data at this point
|
|
869
|
-
logger.log('error', `TLS termination route without TLS data for connection ${connectionId}`, {
|
|
870
|
-
connectionId,
|
|
871
|
-
component: 'route-handler'
|
|
872
|
-
});
|
|
873
|
-
socket.end();
|
|
874
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'tls_error');
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
else {
|
|
878
|
-
logger.log('error', `HttpProxy not available for TLS termination for connection ${connectionId}`, {
|
|
879
|
-
connectionId,
|
|
880
|
-
component: 'route-handler'
|
|
881
|
-
});
|
|
882
|
-
socket.end();
|
|
883
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'no_http_proxy');
|
|
884
|
-
return;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
else {
|
|
889
|
-
// No TLS settings - check if this port should use HttpProxy
|
|
890
|
-
const isHttpProxyPort = this.smartProxy.settings.useHttpProxy?.includes(record.localPort);
|
|
891
|
-
// Debug logging
|
|
892
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
893
|
-
logger.log('debug', `Checking HttpProxy forwarding: port=${record.localPort}, useHttpProxy=${JSON.stringify(this.smartProxy.settings.useHttpProxy)}, isHttpProxyPort=${isHttpProxyPort}, hasHttpProxy=${!!this.smartProxy.httpProxyBridge.getHttpProxy()}`, {
|
|
894
|
-
connectionId,
|
|
895
|
-
localPort: record.localPort,
|
|
896
|
-
useHttpProxy: this.smartProxy.settings.useHttpProxy,
|
|
897
|
-
isHttpProxyPort,
|
|
898
|
-
hasHttpProxy: !!this.smartProxy.httpProxyBridge.getHttpProxy(),
|
|
899
|
-
component: 'route-handler'
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
if (isHttpProxyPort && this.smartProxy.httpProxyBridge.getHttpProxy()) {
|
|
903
|
-
// Forward non-TLS connections to HttpProxy if configured
|
|
904
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
905
|
-
logger.log('info', `Using HttpProxy for non-TLS connection ${connectionId} on port ${record.localPort}`, {
|
|
906
|
-
connectionId,
|
|
907
|
-
port: record.localPort,
|
|
908
|
-
component: 'route-handler'
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
this.smartProxy.httpProxyBridge.forwardToHttpProxy(connectionId, socket, record, initialChunk, this.smartProxy.settings.httpProxyPort || 8443, (reason) => this.smartProxy.connectionManager.cleanupConnection(record, reason));
|
|
912
|
-
return;
|
|
913
|
-
}
|
|
914
|
-
else {
|
|
915
|
-
// Basic forwarding
|
|
916
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
917
|
-
logger.log('info', `Using basic forwarding to ${Array.isArray(selectedTarget.host) ? selectedTarget.host.join(', ') : selectedTarget.host}:${selectedTarget.port} for connection ${connectionId}`, {
|
|
918
|
-
connectionId,
|
|
919
|
-
targetHost: selectedTarget.host,
|
|
920
|
-
targetPort: selectedTarget.port,
|
|
921
|
-
component: 'route-handler'
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
// Get the appropriate host value
|
|
925
|
-
let targetHost;
|
|
926
|
-
if (typeof selectedTarget.host === 'function') {
|
|
927
|
-
// For function-based host, use the same routeContext created earlier
|
|
928
|
-
const hostResult = selectedTarget.host(routeContext);
|
|
929
|
-
targetHost = Array.isArray(hostResult)
|
|
930
|
-
? hostResult[Math.floor(Math.random() * hostResult.length)]
|
|
931
|
-
: hostResult;
|
|
932
|
-
}
|
|
933
|
-
else {
|
|
934
|
-
// For static host value
|
|
935
|
-
targetHost = Array.isArray(selectedTarget.host)
|
|
936
|
-
? selectedTarget.host[Math.floor(Math.random() * selectedTarget.host.length)]
|
|
937
|
-
: selectedTarget.host;
|
|
938
|
-
}
|
|
939
|
-
// Determine port - either function-based, static, or preserve incoming port
|
|
940
|
-
let targetPort;
|
|
941
|
-
if (typeof selectedTarget.port === 'function') {
|
|
942
|
-
targetPort = selectedTarget.port(routeContext);
|
|
943
|
-
}
|
|
944
|
-
else if (selectedTarget.port === 'preserve') {
|
|
945
|
-
targetPort = record.localPort;
|
|
946
|
-
}
|
|
947
|
-
else {
|
|
948
|
-
targetPort = selectedTarget.port;
|
|
949
|
-
}
|
|
950
|
-
// Update the connection record and context with resolved values
|
|
951
|
-
record.targetHost = targetHost;
|
|
952
|
-
record.targetPort = targetPort;
|
|
953
|
-
return this.setupDirectConnection(socket, record, record.lockedDomain, initialChunk, undefined, targetHost, targetPort);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Handle a socket-handler action for a route
|
|
959
|
-
*/
|
|
960
|
-
async handleSocketHandlerAction(socket, record, route, initialChunk) {
|
|
961
|
-
const connectionId = record.id;
|
|
962
|
-
// Store the route config in the connection record for metrics and other uses
|
|
963
|
-
record.routeConfig = route;
|
|
964
|
-
record.routeId = route.id || route.name || 'unnamed';
|
|
965
|
-
// Track connection by route
|
|
966
|
-
this.smartProxy.connectionManager.trackConnectionByRoute(record.routeId, record.id);
|
|
967
|
-
if (!route.action.socketHandler) {
|
|
968
|
-
logger.log('error', 'socket-handler action missing socketHandler function', {
|
|
969
|
-
connectionId,
|
|
970
|
-
routeName: route.name,
|
|
971
|
-
component: 'route-handler'
|
|
972
|
-
});
|
|
973
|
-
socket.destroy();
|
|
974
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'missing_handler');
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
// Track event listeners added by the handler so we can clean them up
|
|
978
|
-
const originalOn = socket.on.bind(socket);
|
|
979
|
-
const originalOnce = socket.once.bind(socket);
|
|
980
|
-
const trackedListeners = [];
|
|
981
|
-
// Override socket.on to track listeners
|
|
982
|
-
socket.on = function (event, listener) {
|
|
983
|
-
trackedListeners.push({ event, listener });
|
|
984
|
-
return originalOn(event, listener);
|
|
985
|
-
};
|
|
986
|
-
// Override socket.once to track listeners
|
|
987
|
-
socket.once = function (event, listener) {
|
|
988
|
-
trackedListeners.push({ event, listener });
|
|
989
|
-
return originalOnce(event, listener);
|
|
990
|
-
};
|
|
991
|
-
// Set up automatic cleanup when socket closes
|
|
992
|
-
const cleanupHandler = () => {
|
|
993
|
-
// Remove all tracked listeners
|
|
994
|
-
for (const { event, listener } of trackedListeners) {
|
|
995
|
-
socket.removeListener(event, listener);
|
|
996
|
-
}
|
|
997
|
-
// Restore original methods
|
|
998
|
-
socket.on = originalOn;
|
|
999
|
-
socket.once = originalOnce;
|
|
1000
|
-
};
|
|
1001
|
-
// Listen for socket close to trigger cleanup
|
|
1002
|
-
originalOnce('close', cleanupHandler);
|
|
1003
|
-
originalOnce('error', cleanupHandler);
|
|
1004
|
-
// Create route context for the handler
|
|
1005
|
-
const routeContext = this.createRouteContext({
|
|
1006
|
-
connectionId: record.id,
|
|
1007
|
-
port: record.localPort,
|
|
1008
|
-
domain: record.lockedDomain,
|
|
1009
|
-
clientIp: record.remoteIP,
|
|
1010
|
-
serverIp: socket.localAddress || '',
|
|
1011
|
-
isTls: record.isTLS || false,
|
|
1012
|
-
tlsVersion: record.tlsVersion,
|
|
1013
|
-
routeName: route.name,
|
|
1014
|
-
routeId: route.id,
|
|
1015
|
-
});
|
|
1016
|
-
try {
|
|
1017
|
-
// Call the handler with the appropriate socket (extract underlying if needed)
|
|
1018
|
-
const handlerSocket = getUnderlyingSocket(socket);
|
|
1019
|
-
const result = route.action.socketHandler(handlerSocket, routeContext);
|
|
1020
|
-
// Handle async handlers properly
|
|
1021
|
-
if (result instanceof Promise) {
|
|
1022
|
-
result
|
|
1023
|
-
.then(() => {
|
|
1024
|
-
// Emit initial chunk after async handler completes
|
|
1025
|
-
if (initialChunk && initialChunk.length > 0) {
|
|
1026
|
-
socket.emit('data', initialChunk);
|
|
1027
|
-
}
|
|
1028
|
-
})
|
|
1029
|
-
.catch(error => {
|
|
1030
|
-
logger.log('error', 'Socket handler error', {
|
|
1031
|
-
connectionId,
|
|
1032
|
-
routeName: route.name,
|
|
1033
|
-
error: error.message,
|
|
1034
|
-
component: 'route-handler'
|
|
1035
|
-
});
|
|
1036
|
-
// Remove all event listeners before destroying to prevent memory leaks
|
|
1037
|
-
socket.removeAllListeners();
|
|
1038
|
-
if (!socket.destroyed) {
|
|
1039
|
-
socket.destroy();
|
|
1040
|
-
}
|
|
1041
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'handler_error');
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
else {
|
|
1045
|
-
// For sync handlers, emit on next tick
|
|
1046
|
-
if (initialChunk && initialChunk.length > 0) {
|
|
1047
|
-
process.nextTick(() => {
|
|
1048
|
-
socket.emit('data', initialChunk);
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
catch (error) {
|
|
1054
|
-
logger.log('error', 'Socket handler error', {
|
|
1055
|
-
connectionId,
|
|
1056
|
-
routeName: route.name,
|
|
1057
|
-
error: error.message,
|
|
1058
|
-
component: 'route-handler'
|
|
1059
|
-
});
|
|
1060
|
-
// Remove all event listeners before destroying to prevent memory leaks
|
|
1061
|
-
socket.removeAllListeners();
|
|
1062
|
-
if (!socket.destroyed) {
|
|
1063
|
-
socket.destroy();
|
|
1064
|
-
}
|
|
1065
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'handler_error');
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* Sets up a direct connection to the target
|
|
1070
|
-
*/
|
|
1071
|
-
setupDirectConnection(socket, record, serverName, initialChunk, overridePort, targetHost, targetPort) {
|
|
1072
|
-
const connectionId = record.id;
|
|
1073
|
-
// Determine target host and port if not provided
|
|
1074
|
-
const finalTargetHost = targetHost || record.targetHost || this.smartProxy.settings.defaults?.target?.host || 'localhost';
|
|
1075
|
-
// Determine target port
|
|
1076
|
-
const finalTargetPort = targetPort ||
|
|
1077
|
-
record.targetPort ||
|
|
1078
|
-
(overridePort !== undefined ? overridePort : this.smartProxy.settings.defaults?.target?.port || 443);
|
|
1079
|
-
// Update record with final target information
|
|
1080
|
-
record.targetHost = finalTargetHost;
|
|
1081
|
-
record.targetPort = finalTargetPort;
|
|
1082
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
1083
|
-
logger.log('info', `Setting up direct connection ${connectionId} to ${finalTargetHost}:${finalTargetPort}`, {
|
|
1084
|
-
connectionId,
|
|
1085
|
-
targetHost: finalTargetHost,
|
|
1086
|
-
targetPort: finalTargetPort,
|
|
1087
|
-
component: 'route-handler'
|
|
1088
|
-
});
|
|
1089
|
-
}
|
|
1090
|
-
// Setup connection options
|
|
1091
|
-
const connectionOptions = {
|
|
1092
|
-
host: finalTargetHost,
|
|
1093
|
-
port: finalTargetPort,
|
|
1094
|
-
};
|
|
1095
|
-
// Preserve source IP if configured
|
|
1096
|
-
if (this.smartProxy.settings.defaults?.preserveSourceIP || this.smartProxy.settings.preserveSourceIP) {
|
|
1097
|
-
connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
|
|
1098
|
-
}
|
|
1099
|
-
// Store initial data if provided
|
|
1100
|
-
if (initialChunk) {
|
|
1101
|
-
// Don't count bytes here - they will be counted when actually forwarded through bidirectional forwarding
|
|
1102
|
-
record.pendingData.push(Buffer.from(initialChunk));
|
|
1103
|
-
record.pendingDataSize = initialChunk.length;
|
|
1104
|
-
}
|
|
1105
|
-
// Create the target socket with immediate error handling
|
|
1106
|
-
const targetSocket = createSocketWithErrorHandler({
|
|
1107
|
-
port: finalTargetPort,
|
|
1108
|
-
host: finalTargetHost,
|
|
1109
|
-
timeout: this.smartProxy.settings.connectionTimeout || 30000, // Connection timeout (default: 30s)
|
|
1110
|
-
onError: (error) => {
|
|
1111
|
-
// Connection failed - clean up everything immediately
|
|
1112
|
-
// Check if connection record is still valid (client might have disconnected)
|
|
1113
|
-
if (record.connectionClosed) {
|
|
1114
|
-
logger.log('debug', `Backend connection failed but client already disconnected for ${connectionId}`, {
|
|
1115
|
-
connectionId,
|
|
1116
|
-
errorCode: error.code,
|
|
1117
|
-
component: 'route-handler'
|
|
1118
|
-
});
|
|
1119
|
-
return;
|
|
1120
|
-
}
|
|
1121
|
-
logger.log('error', `Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${error.message} (${error.code})`, {
|
|
1122
|
-
connectionId,
|
|
1123
|
-
targetHost: finalTargetHost,
|
|
1124
|
-
targetPort: finalTargetPort,
|
|
1125
|
-
errorMessage: error.message,
|
|
1126
|
-
errorCode: error.code,
|
|
1127
|
-
component: 'route-handler'
|
|
1128
|
-
});
|
|
1129
|
-
// Log specific error types for easier debugging
|
|
1130
|
-
if (error.code === 'ECONNREFUSED') {
|
|
1131
|
-
logger.log('error', `Connection ${connectionId}: Target ${finalTargetHost}:${finalTargetPort} refused connection. Check if the target service is running and listening on that port.`, {
|
|
1132
|
-
connectionId,
|
|
1133
|
-
targetHost: finalTargetHost,
|
|
1134
|
-
targetPort: finalTargetPort,
|
|
1135
|
-
recommendation: 'Check if the target service is running and listening on that port.',
|
|
1136
|
-
component: 'route-handler'
|
|
1137
|
-
});
|
|
1138
|
-
}
|
|
1139
|
-
// Resume the incoming socket to prevent it from hanging
|
|
1140
|
-
if (socket && !socket.destroyed) {
|
|
1141
|
-
socket.resume();
|
|
1142
|
-
}
|
|
1143
|
-
// Clean up the incoming socket
|
|
1144
|
-
if (socket && !socket.destroyed) {
|
|
1145
|
-
socket.destroy();
|
|
1146
|
-
}
|
|
1147
|
-
// Clean up the connection record - this is critical!
|
|
1148
|
-
this.smartProxy.connectionManager.cleanupConnection(record, `connection_failed_${error.code || 'unknown'}`);
|
|
1149
|
-
},
|
|
1150
|
-
onConnect: async () => {
|
|
1151
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
1152
|
-
logger.log('info', `Connection ${connectionId} established to target ${finalTargetHost}:${finalTargetPort}`, {
|
|
1153
|
-
connectionId,
|
|
1154
|
-
targetHost: finalTargetHost,
|
|
1155
|
-
targetPort: finalTargetPort,
|
|
1156
|
-
component: 'route-handler'
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
// Clear any error listeners added by createSocketWithErrorHandler
|
|
1160
|
-
targetSocket.removeAllListeners('error');
|
|
1161
|
-
// Add the normal error handler for established connections
|
|
1162
|
-
targetSocket.on('error', this.smartProxy.connectionManager.handleError('outgoing', record));
|
|
1163
|
-
// Check if we should send PROXY protocol header
|
|
1164
|
-
const shouldSendProxyProtocol = record.routeConfig?.action?.sendProxyProtocol ||
|
|
1165
|
-
this.smartProxy.settings.sendProxyProtocol;
|
|
1166
|
-
if (shouldSendProxyProtocol) {
|
|
1167
|
-
try {
|
|
1168
|
-
// Generate PROXY protocol header
|
|
1169
|
-
const proxyInfo = {
|
|
1170
|
-
protocol: (record.remoteIP.includes(':') ? 'TCP6' : 'TCP4'),
|
|
1171
|
-
sourceIP: record.remoteIP,
|
|
1172
|
-
sourcePort: record.remotePort || socket.remotePort || 0,
|
|
1173
|
-
destinationIP: socket.localAddress || '',
|
|
1174
|
-
destinationPort: socket.localPort || 0
|
|
1175
|
-
};
|
|
1176
|
-
const proxyHeader = ProxyProtocolParser.generate(proxyInfo);
|
|
1177
|
-
// Note: PROXY protocol headers are sent to the backend, not to the client
|
|
1178
|
-
// They are internal protocol overhead and shouldn't be counted in client-facing metrics
|
|
1179
|
-
// Send PROXY protocol header first
|
|
1180
|
-
await new Promise((resolve, reject) => {
|
|
1181
|
-
targetSocket.write(proxyHeader, (err) => {
|
|
1182
|
-
if (err) {
|
|
1183
|
-
logger.log('error', `Failed to send PROXY protocol header`, {
|
|
1184
|
-
connectionId,
|
|
1185
|
-
error: err.message,
|
|
1186
|
-
component: 'route-handler'
|
|
1187
|
-
});
|
|
1188
|
-
reject(err);
|
|
1189
|
-
}
|
|
1190
|
-
else {
|
|
1191
|
-
logger.log('info', `PROXY protocol header sent to backend`, {
|
|
1192
|
-
connectionId,
|
|
1193
|
-
targetHost: finalTargetHost,
|
|
1194
|
-
targetPort: finalTargetPort,
|
|
1195
|
-
sourceIP: proxyInfo.sourceIP,
|
|
1196
|
-
sourcePort: proxyInfo.sourcePort,
|
|
1197
|
-
component: 'route-handler'
|
|
1198
|
-
});
|
|
1199
|
-
resolve();
|
|
1200
|
-
}
|
|
1201
|
-
});
|
|
1202
|
-
});
|
|
1203
|
-
}
|
|
1204
|
-
catch (error) {
|
|
1205
|
-
logger.log('error', `Error sending PROXY protocol header`, {
|
|
1206
|
-
connectionId,
|
|
1207
|
-
error: error.message,
|
|
1208
|
-
component: 'route-handler'
|
|
1209
|
-
});
|
|
1210
|
-
// Continue anyway - don't break the connection
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
// Flush any pending data to target
|
|
1214
|
-
if (record.pendingData.length > 0) {
|
|
1215
|
-
const combinedData = Buffer.concat(record.pendingData);
|
|
1216
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
1217
|
-
console.log(`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`);
|
|
1218
|
-
}
|
|
1219
|
-
// Record the initial chunk bytes for metrics
|
|
1220
|
-
record.bytesReceived += combinedData.length;
|
|
1221
|
-
if (this.smartProxy.metricsCollector) {
|
|
1222
|
-
this.smartProxy.metricsCollector.recordBytes(record.id, combinedData.length, 0);
|
|
1223
|
-
}
|
|
1224
|
-
// Write pending data immediately
|
|
1225
|
-
targetSocket.write(combinedData, (err) => {
|
|
1226
|
-
if (err) {
|
|
1227
|
-
logger.log('error', `Error writing pending data to target for connection ${connectionId}: ${err.message}`, {
|
|
1228
|
-
connectionId,
|
|
1229
|
-
error: err.message,
|
|
1230
|
-
component: 'route-handler'
|
|
1231
|
-
});
|
|
1232
|
-
return this.smartProxy.connectionManager.cleanupConnection(record, 'write_error');
|
|
1233
|
-
}
|
|
1234
|
-
});
|
|
1235
|
-
// Clear the buffer now that we've processed it
|
|
1236
|
-
record.pendingData = [];
|
|
1237
|
-
record.pendingDataSize = 0;
|
|
1238
|
-
}
|
|
1239
|
-
// Use centralized bidirectional forwarding setup
|
|
1240
|
-
// Extract underlying sockets for socket-utils functions
|
|
1241
|
-
const incomingSocket = getUnderlyingSocket(socket);
|
|
1242
|
-
setupBidirectionalForwarding(incomingSocket, targetSocket, {
|
|
1243
|
-
onClientData: (chunk) => {
|
|
1244
|
-
record.bytesReceived += chunk.length;
|
|
1245
|
-
this.smartProxy.timeoutManager.updateActivity(record);
|
|
1246
|
-
// Record bytes for metrics
|
|
1247
|
-
if (this.smartProxy.metricsCollector) {
|
|
1248
|
-
this.smartProxy.metricsCollector.recordBytes(record.id, chunk.length, 0);
|
|
1249
|
-
}
|
|
1250
|
-
},
|
|
1251
|
-
onServerData: (chunk) => {
|
|
1252
|
-
record.bytesSent += chunk.length;
|
|
1253
|
-
this.smartProxy.timeoutManager.updateActivity(record);
|
|
1254
|
-
// Record bytes for metrics
|
|
1255
|
-
if (this.smartProxy.metricsCollector) {
|
|
1256
|
-
this.smartProxy.metricsCollector.recordBytes(record.id, 0, chunk.length);
|
|
1257
|
-
}
|
|
1258
|
-
},
|
|
1259
|
-
onCleanup: (reason) => {
|
|
1260
|
-
this.smartProxy.connectionManager.cleanupConnection(record, reason);
|
|
1261
|
-
},
|
|
1262
|
-
enableHalfOpen: false // Default: close both when one closes (required for proxy chains)
|
|
1263
|
-
});
|
|
1264
|
-
// Apply timeouts using TimeoutManager
|
|
1265
|
-
const timeout = this.smartProxy.timeoutManager.getEffectiveInactivityTimeout(record);
|
|
1266
|
-
// Skip timeout for immortal connections (MAX_SAFE_INTEGER would cause issues)
|
|
1267
|
-
if (timeout !== Number.MAX_SAFE_INTEGER) {
|
|
1268
|
-
const safeTimeout = this.smartProxy.timeoutManager.ensureSafeTimeout(timeout);
|
|
1269
|
-
socket.setTimeout(safeTimeout);
|
|
1270
|
-
targetSocket.setTimeout(safeTimeout);
|
|
1271
|
-
}
|
|
1272
|
-
// Log successful connection
|
|
1273
|
-
logger.log('info', `Connection established: ${record.remoteIP} -> ${finalTargetHost}:${finalTargetPort}` +
|
|
1274
|
-
`${serverName ? ` (SNI: ${serverName})` : record.lockedDomain ? ` (Domain: ${record.lockedDomain})` : ''}`, {
|
|
1275
|
-
remoteIP: record.remoteIP,
|
|
1276
|
-
targetHost: finalTargetHost,
|
|
1277
|
-
targetPort: finalTargetPort,
|
|
1278
|
-
sni: serverName || undefined,
|
|
1279
|
-
domain: !serverName && record.lockedDomain ? record.lockedDomain : undefined,
|
|
1280
|
-
component: 'route-handler'
|
|
1281
|
-
});
|
|
1282
|
-
// Add TLS renegotiation handler if needed
|
|
1283
|
-
if (serverName) {
|
|
1284
|
-
// Create connection info object for the existing connection
|
|
1285
|
-
const connInfo = {
|
|
1286
|
-
sourceIp: record.remoteIP,
|
|
1287
|
-
sourcePort: record.incoming.remotePort || 0,
|
|
1288
|
-
destIp: record.incoming.localAddress || '',
|
|
1289
|
-
destPort: record.incoming.localPort || 0,
|
|
1290
|
-
};
|
|
1291
|
-
// Create a renegotiation handler function
|
|
1292
|
-
const renegotiationHandler = this.smartProxy.tlsManager.createRenegotiationHandler(connectionId, serverName, connInfo, (_connectionId, reason) => this.smartProxy.connectionManager.cleanupConnection(record, reason));
|
|
1293
|
-
// Store the handler in the connection record so we can remove it during cleanup
|
|
1294
|
-
record.renegotiationHandler = renegotiationHandler;
|
|
1295
|
-
// Add the handler to the socket
|
|
1296
|
-
socket.on('data', renegotiationHandler);
|
|
1297
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
1298
|
-
logger.log('info', `TLS renegotiation handler installed for connection ${connectionId} with SNI ${serverName}`, {
|
|
1299
|
-
connectionId,
|
|
1300
|
-
serverName,
|
|
1301
|
-
component: 'route-handler'
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
// Set connection timeout
|
|
1306
|
-
record.cleanupTimer = this.smartProxy.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
|
|
1307
|
-
logger.log('warn', `Connection ${connectionId} from ${record.remoteIP} exceeded max lifetime, forcing cleanup`, {
|
|
1308
|
-
connectionId,
|
|
1309
|
-
remoteIP: record.remoteIP,
|
|
1310
|
-
component: 'route-handler'
|
|
1311
|
-
});
|
|
1312
|
-
this.smartProxy.connectionManager.cleanupConnection(record, reason);
|
|
1313
|
-
});
|
|
1314
|
-
// Mark TLS handshake as complete for TLS connections
|
|
1315
|
-
if (record.isTLS) {
|
|
1316
|
-
record.tlsHandshakeComplete = true;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
});
|
|
1320
|
-
// Set outgoing socket immediately so it can be cleaned up if client disconnects
|
|
1321
|
-
record.outgoing = targetSocket;
|
|
1322
|
-
record.outgoingStartTime = Date.now();
|
|
1323
|
-
// Apply socket optimizations
|
|
1324
|
-
targetSocket.setNoDelay(this.smartProxy.settings.noDelay);
|
|
1325
|
-
// Apply keep-alive settings if enabled
|
|
1326
|
-
if (this.smartProxy.settings.keepAlive) {
|
|
1327
|
-
targetSocket.setKeepAlive(true, this.smartProxy.settings.keepAliveInitialDelay);
|
|
1328
|
-
// Apply enhanced TCP keep-alive options if enabled
|
|
1329
|
-
if (this.smartProxy.settings.enableKeepAliveProbes) {
|
|
1330
|
-
try {
|
|
1331
|
-
if ('setKeepAliveProbes' in targetSocket) {
|
|
1332
|
-
targetSocket.setKeepAliveProbes(10);
|
|
1333
|
-
}
|
|
1334
|
-
if ('setKeepAliveInterval' in targetSocket) {
|
|
1335
|
-
targetSocket.setKeepAliveInterval(1000);
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
catch (err) {
|
|
1339
|
-
// Ignore errors - these are optional enhancements
|
|
1340
|
-
if (this.smartProxy.settings.enableDetailedLogging) {
|
|
1341
|
-
logger.log('warn', `Enhanced TCP keep-alive not supported for outgoing socket on connection ${connectionId}: ${err}`, {
|
|
1342
|
-
connectionId,
|
|
1343
|
-
error: err,
|
|
1344
|
-
component: 'route-handler'
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
}
|
|
1350
|
-
// Setup error handlers for incoming socket
|
|
1351
|
-
socket.on('error', this.smartProxy.connectionManager.handleError('incoming', record));
|
|
1352
|
-
// Handle timeouts with keep-alive awareness
|
|
1353
|
-
socket.on('timeout', () => {
|
|
1354
|
-
// For keep-alive connections, just log a warning instead of closing
|
|
1355
|
-
if (record.hasKeepAlive) {
|
|
1356
|
-
logger.log('warn', `Timeout event on incoming keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}. Connection preserved.`, {
|
|
1357
|
-
connectionId,
|
|
1358
|
-
remoteIP: record.remoteIP,
|
|
1359
|
-
timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
|
|
1360
|
-
status: 'Connection preserved',
|
|
1361
|
-
component: 'route-handler'
|
|
1362
|
-
});
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1365
|
-
// For non-keep-alive connections, proceed with normal cleanup
|
|
1366
|
-
logger.log('warn', `Timeout on incoming side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}`, {
|
|
1367
|
-
connectionId,
|
|
1368
|
-
remoteIP: record.remoteIP,
|
|
1369
|
-
timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
|
|
1370
|
-
component: 'route-handler'
|
|
1371
|
-
});
|
|
1372
|
-
if (record.incomingTerminationReason === null) {
|
|
1373
|
-
record.incomingTerminationReason = 'timeout';
|
|
1374
|
-
this.smartProxy.connectionManager.incrementTerminationStat('incoming', 'timeout');
|
|
1375
|
-
}
|
|
1376
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'timeout_incoming');
|
|
1377
|
-
});
|
|
1378
|
-
targetSocket.on('timeout', () => {
|
|
1379
|
-
// For keep-alive connections, just log a warning instead of closing
|
|
1380
|
-
if (record.hasKeepAlive) {
|
|
1381
|
-
logger.log('warn', `Timeout event on outgoing keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}. Connection preserved.`, {
|
|
1382
|
-
connectionId,
|
|
1383
|
-
remoteIP: record.remoteIP,
|
|
1384
|
-
timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
|
|
1385
|
-
status: 'Connection preserved',
|
|
1386
|
-
component: 'route-handler'
|
|
1387
|
-
});
|
|
1388
|
-
return;
|
|
1389
|
-
}
|
|
1390
|
-
// For non-keep-alive connections, proceed with normal cleanup
|
|
1391
|
-
logger.log('warn', `Timeout on outgoing side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000)}`, {
|
|
1392
|
-
connectionId,
|
|
1393
|
-
remoteIP: record.remoteIP,
|
|
1394
|
-
timeout: plugins.prettyMs(this.smartProxy.settings.socketTimeout || 3600000),
|
|
1395
|
-
component: 'route-handler'
|
|
1396
|
-
});
|
|
1397
|
-
if (record.outgoingTerminationReason === null) {
|
|
1398
|
-
record.outgoingTerminationReason = 'timeout';
|
|
1399
|
-
this.smartProxy.connectionManager.incrementTerminationStat('outgoing', 'timeout');
|
|
1400
|
-
}
|
|
1401
|
-
this.smartProxy.connectionManager.cleanupConnection(record, 'timeout_outgoing');
|
|
1402
|
-
});
|
|
1403
|
-
// Apply socket timeouts
|
|
1404
|
-
this.smartProxy.timeoutManager.applySocketTimeouts(record);
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtY29ubmVjdGlvbi1oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJveGllcy9zbWFydC1wcm94eS9yb3V0ZS1jb25uZWN0aW9uLWhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUU1QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDcEQsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sc0NBQXNDLENBQUM7QUFJakYsT0FBTyxFQUFFLGFBQWEsRUFBRSxtQkFBbUIsRUFBRSw0QkFBNEIsRUFBRSw0QkFBNEIsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBQ2xKLE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUNwRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxtQ0FBbUMsQ0FBQztBQUN4RSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUV6RSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUU1RDs7R0FFRztBQUNILE1BQU0sT0FBTyxzQkFBc0I7SUFRakMsWUFDVSxVQUFzQjtRQUF0QixlQUFVLEdBQVYsVUFBVSxDQUFZO1FBUmhDLGlFQUFpRTtRQUNqRSxnRUFBZ0U7UUFDaEUsc0RBQXNEO1FBRXRELG1DQUFtQztRQUM1Qix5QkFBb0IsR0FBRyxJQUFJLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBcUIsQ0FBQztJQUlqRixDQUFDO0lBR0o7O09BRUc7SUFDSyxrQkFBa0IsQ0FBQyxPQWExQjtRQUNDLE9BQU87WUFDTCx5QkFBeUI7WUFDekIsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJO1lBQ2xCLE1BQU0sRUFBRSxPQUFPLENBQUMsTUFBTTtZQUN0QixRQUFRLEVBQUUsT0FBTyxDQUFDLFFB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
|