@push.rocks/smartproxy 13.1.2 → 15.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.
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/proxies/smart-proxy/index.d.ts +5 -3
- package/dist_ts/proxies/smart-proxy/index.js +9 -5
- package/dist_ts/proxies/smart-proxy/models/index.d.ts +2 -0
- package/dist_ts/proxies/smart-proxy/models/index.js +2 -1
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +82 -15
- package/dist_ts/proxies/smart-proxy/models/interfaces.js +10 -1
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +133 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +2 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +55 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +804 -0
- package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +127 -0
- package/dist_ts/proxies/smart-proxy/route-helpers.js +196 -0
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +103 -0
- package/dist_ts/proxies/smart-proxy/route-manager.js +483 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +19 -8
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +239 -46
- package/package.json +2 -2
- package/readme.md +863 -423
- package/readme.plan.md +311 -250
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/proxies/smart-proxy/index.ts +20 -4
- package/ts/proxies/smart-proxy/models/index.ts +4 -0
- package/ts/proxies/smart-proxy/models/interfaces.ts +91 -13
- package/ts/proxies/smart-proxy/models/route-types.ts +184 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +1117 -0
- package/ts/proxies/smart-proxy/route-helpers.ts +344 -0
- package/ts/proxies/smart-proxy/route-manager.ts +587 -0
- package/ts/proxies/smart-proxy/smart-proxy.ts +300 -69
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { isRoutedOptions, isLegacyOptions } from './models/interfaces.js';
|
|
3
|
+
import { ConnectionManager } from './connection-manager.js';
|
|
4
|
+
import { SecurityManager } from './security-manager.js';
|
|
5
|
+
import { DomainConfigManager } from './domain-config-manager.js';
|
|
6
|
+
import { TlsManager } from './tls-manager.js';
|
|
7
|
+
import { NetworkProxyBridge } from './network-proxy-bridge.js';
|
|
8
|
+
import { TimeoutManager } from './timeout-manager.js';
|
|
9
|
+
import { RouteManager } from './route-manager.js';
|
|
10
|
+
/**
|
|
11
|
+
* Handles new connection processing and setup logic with support for route-based configuration
|
|
12
|
+
*/
|
|
13
|
+
export class RouteConnectionHandler {
|
|
14
|
+
constructor(settings, connectionManager, securityManager, domainConfigManager, tlsManager, networkProxyBridge, timeoutManager, routeManager) {
|
|
15
|
+
this.connectionManager = connectionManager;
|
|
16
|
+
this.securityManager = securityManager;
|
|
17
|
+
this.domainConfigManager = domainConfigManager;
|
|
18
|
+
this.tlsManager = tlsManager;
|
|
19
|
+
this.networkProxyBridge = networkProxyBridge;
|
|
20
|
+
this.timeoutManager = timeoutManager;
|
|
21
|
+
this.routeManager = routeManager;
|
|
22
|
+
this.settings = settings;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Handle a new incoming connection
|
|
26
|
+
*/
|
|
27
|
+
handleConnection(socket) {
|
|
28
|
+
const remoteIP = socket.remoteAddress || '';
|
|
29
|
+
const localPort = socket.localPort || 0;
|
|
30
|
+
// Validate IP against rate limits and connection limits
|
|
31
|
+
const ipValidation = this.securityManager.validateIP(remoteIP);
|
|
32
|
+
if (!ipValidation.allowed) {
|
|
33
|
+
console.log(`Connection rejected from ${remoteIP}: ${ipValidation.reason}`);
|
|
34
|
+
socket.end();
|
|
35
|
+
socket.destroy();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
// Create a new connection record
|
|
39
|
+
const record = this.connectionManager.createConnection(socket);
|
|
40
|
+
const connectionId = record.id;
|
|
41
|
+
// Apply socket optimizations
|
|
42
|
+
socket.setNoDelay(this.settings.noDelay);
|
|
43
|
+
// Apply keep-alive settings if enabled
|
|
44
|
+
if (this.settings.keepAlive) {
|
|
45
|
+
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
46
|
+
record.hasKeepAlive = true;
|
|
47
|
+
// Apply enhanced TCP keep-alive options if enabled
|
|
48
|
+
if (this.settings.enableKeepAliveProbes) {
|
|
49
|
+
try {
|
|
50
|
+
// These are platform-specific and may not be available
|
|
51
|
+
if ('setKeepAliveProbes' in socket) {
|
|
52
|
+
socket.setKeepAliveProbes(10);
|
|
53
|
+
}
|
|
54
|
+
if ('setKeepAliveInterval' in socket) {
|
|
55
|
+
socket.setKeepAliveInterval(1000);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// Ignore errors - these are optional enhancements
|
|
60
|
+
if (this.settings.enableDetailedLogging) {
|
|
61
|
+
console.log(`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (this.settings.enableDetailedLogging) {
|
|
67
|
+
console.log(`[${connectionId}] New connection from ${remoteIP} on port ${localPort}. ` +
|
|
68
|
+
`Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
|
|
69
|
+
`Active connections: ${this.connectionManager.getConnectionCount()}`);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log(`New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.connectionManager.getConnectionCount()}`);
|
|
73
|
+
}
|
|
74
|
+
// Start TLS SNI handling
|
|
75
|
+
this.handleTlsConnection(socket, record);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Handle a connection and wait for TLS handshake for SNI extraction if needed
|
|
79
|
+
*/
|
|
80
|
+
handleTlsConnection(socket, record) {
|
|
81
|
+
const connectionId = record.id;
|
|
82
|
+
const localPort = record.localPort;
|
|
83
|
+
let initialDataReceived = false;
|
|
84
|
+
// Set an initial timeout for handshake data
|
|
85
|
+
let initialTimeout = setTimeout(() => {
|
|
86
|
+
if (!initialDataReceived) {
|
|
87
|
+
console.log(`[${connectionId}] Initial data warning (${this.settings.initialDataTimeout}ms) for connection from ${record.remoteIP}`);
|
|
88
|
+
// Add a grace period
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
if (!initialDataReceived) {
|
|
91
|
+
console.log(`[${connectionId}] Final initial data timeout after grace period`);
|
|
92
|
+
if (record.incomingTerminationReason === null) {
|
|
93
|
+
record.incomingTerminationReason = 'initial_timeout';
|
|
94
|
+
this.connectionManager.incrementTerminationStat('incoming', 'initial_timeout');
|
|
95
|
+
}
|
|
96
|
+
socket.end();
|
|
97
|
+
this.connectionManager.cleanupConnection(record, 'initial_timeout');
|
|
98
|
+
}
|
|
99
|
+
}, 30000);
|
|
100
|
+
}
|
|
101
|
+
}, this.settings.initialDataTimeout);
|
|
102
|
+
// Make sure timeout doesn't keep the process alive
|
|
103
|
+
if (initialTimeout.unref) {
|
|
104
|
+
initialTimeout.unref();
|
|
105
|
+
}
|
|
106
|
+
// Set up error handler
|
|
107
|
+
socket.on('error', this.connectionManager.handleError('incoming', record));
|
|
108
|
+
// First data handler to capture initial TLS handshake
|
|
109
|
+
socket.once('data', (chunk) => {
|
|
110
|
+
// Clear the initial timeout since we've received data
|
|
111
|
+
if (initialTimeout) {
|
|
112
|
+
clearTimeout(initialTimeout);
|
|
113
|
+
initialTimeout = null;
|
|
114
|
+
}
|
|
115
|
+
initialDataReceived = true;
|
|
116
|
+
record.hasReceivedInitialData = true;
|
|
117
|
+
// Block non-TLS connections on port 443
|
|
118
|
+
if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) {
|
|
119
|
+
console.log(`[${connectionId}] Non-TLS connection detected on port 443. ` +
|
|
120
|
+
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`);
|
|
121
|
+
if (record.incomingTerminationReason === null) {
|
|
122
|
+
record.incomingTerminationReason = 'non_tls_blocked';
|
|
123
|
+
this.connectionManager.incrementTerminationStat('incoming', 'non_tls_blocked');
|
|
124
|
+
}
|
|
125
|
+
socket.end();
|
|
126
|
+
this.connectionManager.cleanupConnection(record, 'non_tls_blocked');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// Check if this looks like a TLS handshake
|
|
130
|
+
let serverName = '';
|
|
131
|
+
if (this.tlsManager.isTlsHandshake(chunk)) {
|
|
132
|
+
record.isTLS = true;
|
|
133
|
+
// Check for ClientHello to extract SNI
|
|
134
|
+
if (this.tlsManager.isClientHello(chunk)) {
|
|
135
|
+
// Create connection info for SNI extraction
|
|
136
|
+
const connInfo = {
|
|
137
|
+
sourceIp: record.remoteIP,
|
|
138
|
+
sourcePort: socket.remotePort || 0,
|
|
139
|
+
destIp: socket.localAddress || '',
|
|
140
|
+
destPort: socket.localPort || 0,
|
|
141
|
+
};
|
|
142
|
+
// Extract SNI
|
|
143
|
+
serverName = this.tlsManager.extractSNI(chunk, connInfo) || '';
|
|
144
|
+
// Lock the connection to the negotiated SNI
|
|
145
|
+
record.lockedDomain = serverName;
|
|
146
|
+
// Check if we should reject connections without SNI
|
|
147
|
+
if (!serverName && this.settings.allowSessionTicket === false) {
|
|
148
|
+
console.log(`[${connectionId}] No SNI detected in TLS ClientHello; sending TLS alert.`);
|
|
149
|
+
if (record.incomingTerminationReason === null) {
|
|
150
|
+
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
|
|
151
|
+
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
|
|
152
|
+
}
|
|
153
|
+
const alert = Buffer.from([0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x70]);
|
|
154
|
+
try {
|
|
155
|
+
socket.cork();
|
|
156
|
+
socket.write(alert);
|
|
157
|
+
socket.uncork();
|
|
158
|
+
socket.end();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
socket.end();
|
|
162
|
+
}
|
|
163
|
+
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (this.settings.enableDetailedLogging) {
|
|
167
|
+
console.log(`[${connectionId}] TLS connection with SNI: ${serverName || '(empty)'}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Find the appropriate route for this connection
|
|
172
|
+
this.routeConnection(socket, record, serverName, chunk);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Route the connection based on match criteria
|
|
177
|
+
*/
|
|
178
|
+
routeConnection(socket, record, serverName, initialChunk) {
|
|
179
|
+
const connectionId = record.id;
|
|
180
|
+
const localPort = record.localPort;
|
|
181
|
+
const remoteIP = record.remoteIP;
|
|
182
|
+
// Find matching route
|
|
183
|
+
const routeMatch = this.routeManager.findMatchingRoute({
|
|
184
|
+
port: localPort,
|
|
185
|
+
domain: serverName,
|
|
186
|
+
clientIp: remoteIP,
|
|
187
|
+
path: undefined, // We don't have path info at this point
|
|
188
|
+
tlsVersion: undefined // We don't extract TLS version yet
|
|
189
|
+
});
|
|
190
|
+
if (!routeMatch) {
|
|
191
|
+
console.log(`[${connectionId}] No route found for ${serverName || 'connection'} on port ${localPort}`);
|
|
192
|
+
// Fall back to legacy matching if we're using a hybrid configuration
|
|
193
|
+
const domainConfig = serverName
|
|
194
|
+
? this.domainConfigManager.findDomainConfig(serverName)
|
|
195
|
+
: this.domainConfigManager.findDomainConfigForPort(localPort);
|
|
196
|
+
if (domainConfig) {
|
|
197
|
+
if (this.settings.enableDetailedLogging) {
|
|
198
|
+
console.log(`[${connectionId}] Using legacy domain configuration for ${serverName || 'port ' + localPort}`);
|
|
199
|
+
}
|
|
200
|
+
// Associate this domain config with the connection
|
|
201
|
+
record.domainConfig = domainConfig;
|
|
202
|
+
// Handle the connection using the legacy setup
|
|
203
|
+
return this.handleLegacyConnection(socket, record, serverName, domainConfig, initialChunk);
|
|
204
|
+
}
|
|
205
|
+
// No matching route or domain config, use default/fallback handling
|
|
206
|
+
console.log(`[${connectionId}] Using default route handling for connection`);
|
|
207
|
+
// Check default security settings
|
|
208
|
+
const defaultSecuritySettings = this.settings.defaults?.security;
|
|
209
|
+
if (defaultSecuritySettings) {
|
|
210
|
+
if (defaultSecuritySettings.allowedIPs && defaultSecuritySettings.allowedIPs.length > 0) {
|
|
211
|
+
const isAllowed = this.securityManager.isIPAuthorized(remoteIP, defaultSecuritySettings.allowedIPs, defaultSecuritySettings.blockedIPs || []);
|
|
212
|
+
if (!isAllowed) {
|
|
213
|
+
console.log(`[${connectionId}] IP ${remoteIP} not in default allowed list`);
|
|
214
|
+
socket.end();
|
|
215
|
+
this.connectionManager.cleanupConnection(record, 'ip_blocked');
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) {
|
|
221
|
+
// Legacy default IP restrictions
|
|
222
|
+
const isAllowed = this.securityManager.isIPAuthorized(remoteIP, this.settings.defaultAllowedIPs, this.settings.defaultBlockedIPs || []);
|
|
223
|
+
if (!isAllowed) {
|
|
224
|
+
console.log(`[${connectionId}] IP ${remoteIP} not in default allowed list`);
|
|
225
|
+
socket.end();
|
|
226
|
+
this.connectionManager.cleanupConnection(record, 'ip_blocked');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// Setup direct connection with default settings
|
|
231
|
+
let targetHost;
|
|
232
|
+
let targetPort;
|
|
233
|
+
if (isRoutedOptions(this.settings) && this.settings.defaults?.target) {
|
|
234
|
+
// Use defaults from routed configuration
|
|
235
|
+
targetHost = this.settings.defaults.target.host;
|
|
236
|
+
targetPort = this.settings.defaults.target.port;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// Fall back to legacy settings
|
|
240
|
+
targetHost = this.settings.targetIP || 'localhost';
|
|
241
|
+
targetPort = this.settings.toPort;
|
|
242
|
+
}
|
|
243
|
+
return this.setupDirectConnection(socket, record, undefined, serverName, initialChunk, undefined, targetHost, targetPort);
|
|
244
|
+
}
|
|
245
|
+
// A matching route was found
|
|
246
|
+
const route = routeMatch.route;
|
|
247
|
+
if (this.settings.enableDetailedLogging) {
|
|
248
|
+
console.log(`[${connectionId}] Route matched: "${route.name || 'unnamed'}" for ${serverName || 'connection'} on port ${localPort}`);
|
|
249
|
+
}
|
|
250
|
+
// Handle the route based on its action type
|
|
251
|
+
switch (route.action.type) {
|
|
252
|
+
case 'forward':
|
|
253
|
+
return this.handleForwardAction(socket, record, route, initialChunk);
|
|
254
|
+
case 'redirect':
|
|
255
|
+
return this.handleRedirectAction(socket, record, route);
|
|
256
|
+
case 'block':
|
|
257
|
+
return this.handleBlockAction(socket, record, route);
|
|
258
|
+
default:
|
|
259
|
+
console.log(`[${connectionId}] Unknown action type: ${route.action.type}`);
|
|
260
|
+
socket.end();
|
|
261
|
+
this.connectionManager.cleanupConnection(record, 'unknown_action');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Handle a forward action for a route
|
|
266
|
+
*/
|
|
267
|
+
handleForwardAction(socket, record, route, initialChunk) {
|
|
268
|
+
const connectionId = record.id;
|
|
269
|
+
const action = route.action;
|
|
270
|
+
// We should have a target configuration for forwarding
|
|
271
|
+
if (!action.target) {
|
|
272
|
+
console.log(`[${connectionId}] Forward action missing target configuration`);
|
|
273
|
+
socket.end();
|
|
274
|
+
this.connectionManager.cleanupConnection(record, 'missing_target');
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// Determine if this needs TLS handling
|
|
278
|
+
if (action.tls) {
|
|
279
|
+
switch (action.tls.mode) {
|
|
280
|
+
case 'passthrough':
|
|
281
|
+
// For TLS passthrough, just forward directly
|
|
282
|
+
if (this.settings.enableDetailedLogging) {
|
|
283
|
+
console.log(`[${connectionId}] Using TLS passthrough to ${action.target.host}`);
|
|
284
|
+
}
|
|
285
|
+
// Allow for array of hosts
|
|
286
|
+
const targetHost = Array.isArray(action.target.host)
|
|
287
|
+
? action.target.host[Math.floor(Math.random() * action.target.host.length)]
|
|
288
|
+
: action.target.host;
|
|
289
|
+
// Determine target port - either target port or preserve incoming port
|
|
290
|
+
const targetPort = action.target.preservePort ? record.localPort : action.target.port;
|
|
291
|
+
return this.setupDirectConnection(socket, record, undefined, record.lockedDomain, initialChunk, undefined, targetHost, targetPort);
|
|
292
|
+
case 'terminate':
|
|
293
|
+
case 'terminate-and-reencrypt':
|
|
294
|
+
// For TLS termination, use NetworkProxy
|
|
295
|
+
if (this.networkProxyBridge.getNetworkProxy()) {
|
|
296
|
+
if (this.settings.enableDetailedLogging) {
|
|
297
|
+
console.log(`[${connectionId}] Using NetworkProxy for TLS termination to ${action.target.host}`);
|
|
298
|
+
}
|
|
299
|
+
// If we have an initial chunk with TLS data, start processing it
|
|
300
|
+
if (initialChunk && record.isTLS) {
|
|
301
|
+
return this.networkProxyBridge.forwardToNetworkProxy(connectionId, socket, record, initialChunk, this.settings.networkProxyPort, (reason) => this.connectionManager.initiateCleanupOnce(record, reason));
|
|
302
|
+
}
|
|
303
|
+
// This shouldn't normally happen - we should have TLS data at this point
|
|
304
|
+
console.log(`[${connectionId}] TLS termination route without TLS data`);
|
|
305
|
+
socket.end();
|
|
306
|
+
this.connectionManager.cleanupConnection(record, 'tls_error');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
console.log(`[${connectionId}] NetworkProxy not available for TLS termination`);
|
|
311
|
+
socket.end();
|
|
312
|
+
this.connectionManager.cleanupConnection(record, 'no_network_proxy');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// No TLS settings - basic forwarding
|
|
319
|
+
if (this.settings.enableDetailedLogging) {
|
|
320
|
+
console.log(`[${connectionId}] Using basic forwarding to ${action.target.host}:${action.target.port}`);
|
|
321
|
+
}
|
|
322
|
+
// Allow for array of hosts
|
|
323
|
+
const targetHost = Array.isArray(action.target.host)
|
|
324
|
+
? action.target.host[Math.floor(Math.random() * action.target.host.length)]
|
|
325
|
+
: action.target.host;
|
|
326
|
+
// Determine target port - either target port or preserve incoming port
|
|
327
|
+
const targetPort = action.target.preservePort ? record.localPort : action.target.port;
|
|
328
|
+
return this.setupDirectConnection(socket, record, undefined, record.lockedDomain, initialChunk, undefined, targetHost, targetPort);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Handle a redirect action for a route
|
|
333
|
+
*/
|
|
334
|
+
handleRedirectAction(socket, record, route) {
|
|
335
|
+
const connectionId = record.id;
|
|
336
|
+
const action = route.action;
|
|
337
|
+
// We should have a redirect configuration
|
|
338
|
+
if (!action.redirect) {
|
|
339
|
+
console.log(`[${connectionId}] Redirect action missing redirect configuration`);
|
|
340
|
+
socket.end();
|
|
341
|
+
this.connectionManager.cleanupConnection(record, 'missing_redirect');
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// For TLS connections, we can't do redirects at the TCP level
|
|
345
|
+
if (record.isTLS) {
|
|
346
|
+
console.log(`[${connectionId}] Cannot redirect TLS connection at TCP level`);
|
|
347
|
+
socket.end();
|
|
348
|
+
this.connectionManager.cleanupConnection(record, 'tls_redirect_error');
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
// Wait for the first HTTP request to perform the redirect
|
|
352
|
+
const dataListeners = [];
|
|
353
|
+
const httpDataHandler = (chunk) => {
|
|
354
|
+
// Remove all data listeners to avoid duplicated processing
|
|
355
|
+
for (const listener of dataListeners) {
|
|
356
|
+
socket.removeListener('data', listener);
|
|
357
|
+
}
|
|
358
|
+
// Parse HTTP request to get path
|
|
359
|
+
try {
|
|
360
|
+
const headersEnd = chunk.indexOf('\r\n\r\n');
|
|
361
|
+
if (headersEnd === -1) {
|
|
362
|
+
// Not a complete HTTP request, need more data
|
|
363
|
+
socket.once('data', httpDataHandler);
|
|
364
|
+
dataListeners.push(httpDataHandler);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const httpHeaders = chunk.slice(0, headersEnd).toString();
|
|
368
|
+
const requestLine = httpHeaders.split('\r\n')[0];
|
|
369
|
+
const [method, path] = requestLine.split(' ');
|
|
370
|
+
// Extract Host header
|
|
371
|
+
const hostMatch = httpHeaders.match(/Host: (.+?)(\r\n|\r|\n|$)/i);
|
|
372
|
+
const host = hostMatch ? hostMatch[1].trim() : record.lockedDomain || '';
|
|
373
|
+
// Process the redirect URL with template variables
|
|
374
|
+
let redirectUrl = action.redirect.to;
|
|
375
|
+
redirectUrl = redirectUrl.replace(/\{domain\}/g, host);
|
|
376
|
+
redirectUrl = redirectUrl.replace(/\{path\}/g, path || '');
|
|
377
|
+
redirectUrl = redirectUrl.replace(/\{port\}/g, record.localPort.toString());
|
|
378
|
+
// Prepare the HTTP redirect response
|
|
379
|
+
const redirectResponse = [
|
|
380
|
+
`HTTP/1.1 ${action.redirect.status} Moved`,
|
|
381
|
+
`Location: ${redirectUrl}`,
|
|
382
|
+
'Connection: close',
|
|
383
|
+
'Content-Length: 0',
|
|
384
|
+
'',
|
|
385
|
+
''
|
|
386
|
+
].join('\r\n');
|
|
387
|
+
if (this.settings.enableDetailedLogging) {
|
|
388
|
+
console.log(`[${connectionId}] Redirecting to ${redirectUrl} with status ${action.redirect.status}`);
|
|
389
|
+
}
|
|
390
|
+
// Send the redirect response
|
|
391
|
+
socket.end(redirectResponse);
|
|
392
|
+
this.connectionManager.initiateCleanupOnce(record, 'redirect_complete');
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
console.log(`[${connectionId}] Error processing HTTP redirect: ${err}`);
|
|
396
|
+
socket.end();
|
|
397
|
+
this.connectionManager.initiateCleanupOnce(record, 'redirect_error');
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
// Setup the HTTP data handler
|
|
401
|
+
socket.once('data', httpDataHandler);
|
|
402
|
+
dataListeners.push(httpDataHandler);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Handle a block action for a route
|
|
406
|
+
*/
|
|
407
|
+
handleBlockAction(socket, record, route) {
|
|
408
|
+
const connectionId = record.id;
|
|
409
|
+
if (this.settings.enableDetailedLogging) {
|
|
410
|
+
console.log(`[${connectionId}] Blocking connection based on route "${route.name || 'unnamed'}"`);
|
|
411
|
+
}
|
|
412
|
+
// Simply close the connection
|
|
413
|
+
socket.end();
|
|
414
|
+
this.connectionManager.initiateCleanupOnce(record, 'route_blocked');
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Handle a connection using legacy domain configuration
|
|
418
|
+
*/
|
|
419
|
+
handleLegacyConnection(socket, record, serverName, domainConfig, initialChunk) {
|
|
420
|
+
const connectionId = record.id;
|
|
421
|
+
// Get the forwarding type for this domain
|
|
422
|
+
const forwardingType = this.domainConfigManager.getForwardingType(domainConfig);
|
|
423
|
+
// IP validation
|
|
424
|
+
const ipRules = this.domainConfigManager.getEffectiveIPRules(domainConfig);
|
|
425
|
+
if (!this.securityManager.isIPAuthorized(record.remoteIP, ipRules.allowedIPs, ipRules.blockedIPs)) {
|
|
426
|
+
console.log(`[${connectionId}] Connection rejected: IP ${record.remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`);
|
|
427
|
+
socket.end();
|
|
428
|
+
this.connectionManager.initiateCleanupOnce(record, 'ip_blocked');
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
// Handle based on forwarding type
|
|
432
|
+
switch (forwardingType) {
|
|
433
|
+
case 'http-only':
|
|
434
|
+
// For HTTP-only configs with TLS traffic
|
|
435
|
+
if (record.isTLS) {
|
|
436
|
+
console.log(`[${connectionId}] Received TLS connection for HTTP-only domain ${serverName}`);
|
|
437
|
+
socket.end();
|
|
438
|
+
this.connectionManager.initiateCleanupOnce(record, 'wrong_protocol');
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
case 'https-passthrough':
|
|
443
|
+
// For TLS passthrough with TLS traffic
|
|
444
|
+
if (record.isTLS) {
|
|
445
|
+
try {
|
|
446
|
+
const handler = this.domainConfigManager.getForwardingHandler(domainConfig);
|
|
447
|
+
if (this.settings.enableDetailedLogging) {
|
|
448
|
+
console.log(`[${connectionId}] Using forwarding handler for SNI passthrough to ${serverName}`);
|
|
449
|
+
}
|
|
450
|
+
// Handle the connection using the handler
|
|
451
|
+
return handler.handleConnection(socket);
|
|
452
|
+
}
|
|
453
|
+
catch (err) {
|
|
454
|
+
console.log(`[${connectionId}] Error using forwarding handler: ${err}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
break;
|
|
458
|
+
case 'https-terminate-to-http':
|
|
459
|
+
case 'https-terminate-to-https':
|
|
460
|
+
// For TLS termination with TLS traffic
|
|
461
|
+
if (record.isTLS) {
|
|
462
|
+
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
|
|
463
|
+
if (this.settings.enableDetailedLogging) {
|
|
464
|
+
console.log(`[${connectionId}] Using TLS termination (${forwardingType}) for ${serverName} on port ${networkProxyPort}`);
|
|
465
|
+
}
|
|
466
|
+
// Forward to NetworkProxy with domain-specific port
|
|
467
|
+
return this.networkProxyBridge.forwardToNetworkProxy(connectionId, socket, record, initialChunk, networkProxyPort, (reason) => this.connectionManager.initiateCleanupOnce(record, reason));
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
// If we're still here, use the forwarding handler if available
|
|
472
|
+
try {
|
|
473
|
+
const handler = this.domainConfigManager.getForwardingHandler(domainConfig);
|
|
474
|
+
if (this.settings.enableDetailedLogging) {
|
|
475
|
+
console.log(`[${connectionId}] Using general forwarding handler for domain ${serverName || 'unknown'}`);
|
|
476
|
+
}
|
|
477
|
+
// Handle the connection using the handler
|
|
478
|
+
return handler.handleConnection(socket);
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
console.log(`[${connectionId}] Error using forwarding handler: ${err}`);
|
|
482
|
+
}
|
|
483
|
+
// Fallback: set up direct connection
|
|
484
|
+
const targetIp = this.domainConfigManager.getTargetIP(domainConfig);
|
|
485
|
+
const targetPort = this.domainConfigManager.getTargetPort(domainConfig, this.settings.toPort);
|
|
486
|
+
return this.setupDirectConnection(socket, record, domainConfig, serverName, initialChunk, undefined, targetIp, targetPort);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Sets up a direct connection to the target
|
|
490
|
+
*/
|
|
491
|
+
setupDirectConnection(socket, record, domainConfig, serverName, initialChunk, overridePort, targetHost, targetPort) {
|
|
492
|
+
const connectionId = record.id;
|
|
493
|
+
// Determine target host and port if not provided
|
|
494
|
+
const finalTargetHost = targetHost || (domainConfig
|
|
495
|
+
? this.domainConfigManager.getTargetIP(domainConfig)
|
|
496
|
+
: this.settings.defaults?.target?.host
|
|
497
|
+
? this.settings.defaults.target.host
|
|
498
|
+
: this.settings.targetIP);
|
|
499
|
+
// Determine target port - first try explicit port, then forwarding config, then fallback
|
|
500
|
+
const finalTargetPort = targetPort || (overridePort !== undefined
|
|
501
|
+
? overridePort
|
|
502
|
+
: domainConfig
|
|
503
|
+
? this.domainConfigManager.getTargetPort(domainConfig, this.settings.toPort)
|
|
504
|
+
: this.settings.defaults?.target?.port
|
|
505
|
+
? this.settings.defaults.target.port
|
|
506
|
+
: this.settings.toPort);
|
|
507
|
+
// Setup connection options
|
|
508
|
+
const connectionOptions = {
|
|
509
|
+
host: finalTargetHost,
|
|
510
|
+
port: finalTargetPort,
|
|
511
|
+
};
|
|
512
|
+
// Preserve source IP if configured
|
|
513
|
+
if (this.settings.defaults?.preserveSourceIP || this.settings.preserveSourceIP) {
|
|
514
|
+
connectionOptions.localAddress = record.remoteIP.replace('::ffff:', '');
|
|
515
|
+
}
|
|
516
|
+
// Create a safe queue for incoming data
|
|
517
|
+
const dataQueue = [];
|
|
518
|
+
let queueSize = 0;
|
|
519
|
+
let processingQueue = false;
|
|
520
|
+
let drainPending = false;
|
|
521
|
+
let pipingEstablished = false;
|
|
522
|
+
// Pause the incoming socket to prevent buffer overflows
|
|
523
|
+
socket.pause();
|
|
524
|
+
// Function to safely process the data queue without losing events
|
|
525
|
+
const processDataQueue = () => {
|
|
526
|
+
if (processingQueue || dataQueue.length === 0 || pipingEstablished)
|
|
527
|
+
return;
|
|
528
|
+
processingQueue = true;
|
|
529
|
+
try {
|
|
530
|
+
// Process all queued chunks with the current active handler
|
|
531
|
+
while (dataQueue.length > 0) {
|
|
532
|
+
const chunk = dataQueue.shift();
|
|
533
|
+
queueSize -= chunk.length;
|
|
534
|
+
// Once piping is established, we shouldn't get here,
|
|
535
|
+
// but just in case, pass to the outgoing socket directly
|
|
536
|
+
if (pipingEstablished && record.outgoing) {
|
|
537
|
+
record.outgoing.write(chunk);
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
// Track bytes received
|
|
541
|
+
record.bytesReceived += chunk.length;
|
|
542
|
+
// Check for TLS handshake
|
|
543
|
+
if (!record.isTLS && this.tlsManager.isTlsHandshake(chunk)) {
|
|
544
|
+
record.isTLS = true;
|
|
545
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
546
|
+
console.log(`[${connectionId}] TLS handshake detected in tempDataHandler, ${chunk.length} bytes`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
// Check if adding this chunk would exceed the buffer limit
|
|
550
|
+
const newSize = record.pendingDataSize + chunk.length;
|
|
551
|
+
if (this.settings.maxPendingDataSize && newSize > this.settings.maxPendingDataSize) {
|
|
552
|
+
console.log(`[${connectionId}] Buffer limit exceeded for connection from ${record.remoteIP}: ${newSize} bytes > ${this.settings.maxPendingDataSize} bytes`);
|
|
553
|
+
socket.end(); // Gracefully close the socket
|
|
554
|
+
this.connectionManager.initiateCleanupOnce(record, 'buffer_limit_exceeded');
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
// Buffer the chunk and update the size counter
|
|
558
|
+
record.pendingData.push(Buffer.from(chunk));
|
|
559
|
+
record.pendingDataSize = newSize;
|
|
560
|
+
this.timeoutManager.updateActivity(record);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
finally {
|
|
564
|
+
processingQueue = false;
|
|
565
|
+
// If there's a pending drain and we've processed everything,
|
|
566
|
+
// signal we're ready for more data if we haven't established piping yet
|
|
567
|
+
if (drainPending && dataQueue.length === 0 && !pipingEstablished) {
|
|
568
|
+
drainPending = false;
|
|
569
|
+
socket.resume();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
// Unified data handler that safely queues incoming data
|
|
574
|
+
const safeDataHandler = (chunk) => {
|
|
575
|
+
// If piping is already established, just let the pipe handle it
|
|
576
|
+
if (pipingEstablished)
|
|
577
|
+
return;
|
|
578
|
+
// Add to our queue for orderly processing
|
|
579
|
+
dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe
|
|
580
|
+
queueSize += chunk.length;
|
|
581
|
+
// If queue is getting large, pause socket until we catch up
|
|
582
|
+
if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) {
|
|
583
|
+
socket.pause();
|
|
584
|
+
drainPending = true;
|
|
585
|
+
}
|
|
586
|
+
// Process the queue
|
|
587
|
+
processDataQueue();
|
|
588
|
+
};
|
|
589
|
+
// Add our safe data handler
|
|
590
|
+
socket.on('data', safeDataHandler);
|
|
591
|
+
// Add initial chunk to pending data if present
|
|
592
|
+
if (initialChunk) {
|
|
593
|
+
record.bytesReceived += initialChunk.length;
|
|
594
|
+
record.pendingData.push(Buffer.from(initialChunk));
|
|
595
|
+
record.pendingDataSize = initialChunk.length;
|
|
596
|
+
}
|
|
597
|
+
// Create the target socket but don't set up piping immediately
|
|
598
|
+
const targetSocket = plugins.net.connect(connectionOptions);
|
|
599
|
+
record.outgoing = targetSocket;
|
|
600
|
+
record.outgoingStartTime = Date.now();
|
|
601
|
+
// Apply socket optimizations
|
|
602
|
+
targetSocket.setNoDelay(this.settings.noDelay);
|
|
603
|
+
// Apply keep-alive settings to the outgoing connection as well
|
|
604
|
+
if (this.settings.keepAlive) {
|
|
605
|
+
targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
606
|
+
// Apply enhanced TCP keep-alive options if enabled
|
|
607
|
+
if (this.settings.enableKeepAliveProbes) {
|
|
608
|
+
try {
|
|
609
|
+
if ('setKeepAliveProbes' in targetSocket) {
|
|
610
|
+
targetSocket.setKeepAliveProbes(10);
|
|
611
|
+
}
|
|
612
|
+
if ('setKeepAliveInterval' in targetSocket) {
|
|
613
|
+
targetSocket.setKeepAliveInterval(1000);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
catch (err) {
|
|
617
|
+
// Ignore errors - these are optional enhancements
|
|
618
|
+
if (this.settings.enableDetailedLogging) {
|
|
619
|
+
console.log(`[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
// Setup specific error handler for connection phase
|
|
625
|
+
targetSocket.once('error', (err) => {
|
|
626
|
+
// This handler runs only once during the initial connection phase
|
|
627
|
+
const code = err.code;
|
|
628
|
+
console.log(`[${connectionId}] Connection setup error to ${finalTargetHost}:${connectionOptions.port}: ${err.message} (${code})`);
|
|
629
|
+
// Resume the incoming socket to prevent it from hanging
|
|
630
|
+
socket.resume();
|
|
631
|
+
if (code === 'ECONNREFUSED') {
|
|
632
|
+
console.log(`[${connectionId}] Target ${finalTargetHost}:${connectionOptions.port} refused connection`);
|
|
633
|
+
}
|
|
634
|
+
else if (code === 'ETIMEDOUT') {
|
|
635
|
+
console.log(`[${connectionId}] Connection to ${finalTargetHost}:${connectionOptions.port} timed out`);
|
|
636
|
+
}
|
|
637
|
+
else if (code === 'ECONNRESET') {
|
|
638
|
+
console.log(`[${connectionId}] Connection to ${finalTargetHost}:${connectionOptions.port} was reset`);
|
|
639
|
+
}
|
|
640
|
+
else if (code === 'EHOSTUNREACH') {
|
|
641
|
+
console.log(`[${connectionId}] Host ${finalTargetHost} is unreachable`);
|
|
642
|
+
}
|
|
643
|
+
// Clear any existing error handler after connection phase
|
|
644
|
+
targetSocket.removeAllListeners('error');
|
|
645
|
+
// Re-add the normal error handler for established connections
|
|
646
|
+
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
647
|
+
if (record.outgoingTerminationReason === null) {
|
|
648
|
+
record.outgoingTerminationReason = 'connection_failed';
|
|
649
|
+
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
|
650
|
+
}
|
|
651
|
+
// If we have a forwarding handler for this domain, let it handle the error
|
|
652
|
+
if (domainConfig) {
|
|
653
|
+
try {
|
|
654
|
+
const forwardingHandler = this.domainConfigManager.getForwardingHandler(domainConfig);
|
|
655
|
+
forwardingHandler.emit('connection_error', {
|
|
656
|
+
socket,
|
|
657
|
+
error: err,
|
|
658
|
+
connectionId
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
catch (handlerErr) {
|
|
662
|
+
// If getting the handler fails, just log and continue with normal cleanup
|
|
663
|
+
console.log(`Error getting forwarding handler for error handling: ${handlerErr}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
// Clean up the connection
|
|
667
|
+
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
|
668
|
+
});
|
|
669
|
+
// Setup close handler
|
|
670
|
+
targetSocket.on('close', this.connectionManager.handleClose('outgoing', record));
|
|
671
|
+
socket.on('close', this.connectionManager.handleClose('incoming', record));
|
|
672
|
+
// Handle timeouts with keep-alive awareness
|
|
673
|
+
socket.on('timeout', () => {
|
|
674
|
+
// For keep-alive connections, just log a warning instead of closing
|
|
675
|
+
if (record.hasKeepAlive) {
|
|
676
|
+
console.log(`[${connectionId}] Timeout event on incoming keep-alive connection from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
// For non-keep-alive connections, proceed with normal cleanup
|
|
680
|
+
console.log(`[${connectionId}] Timeout on incoming side from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`);
|
|
681
|
+
if (record.incomingTerminationReason === null) {
|
|
682
|
+
record.incomingTerminationReason = 'timeout';
|
|
683
|
+
this.connectionManager.incrementTerminationStat('incoming', 'timeout');
|
|
684
|
+
}
|
|
685
|
+
this.connectionManager.initiateCleanupOnce(record, 'timeout_incoming');
|
|
686
|
+
});
|
|
687
|
+
targetSocket.on('timeout', () => {
|
|
688
|
+
// For keep-alive connections, just log a warning instead of closing
|
|
689
|
+
if (record.hasKeepAlive) {
|
|
690
|
+
console.log(`[${connectionId}] Timeout event on outgoing keep-alive connection from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
// For non-keep-alive connections, proceed with normal cleanup
|
|
694
|
+
console.log(`[${connectionId}] Timeout on outgoing side from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`);
|
|
695
|
+
if (record.outgoingTerminationReason === null) {
|
|
696
|
+
record.outgoingTerminationReason = 'timeout';
|
|
697
|
+
this.connectionManager.incrementTerminationStat('outgoing', 'timeout');
|
|
698
|
+
}
|
|
699
|
+
this.connectionManager.initiateCleanupOnce(record, 'timeout_outgoing');
|
|
700
|
+
});
|
|
701
|
+
// Apply socket timeouts
|
|
702
|
+
this.timeoutManager.applySocketTimeouts(record);
|
|
703
|
+
// Track outgoing data for bytes counting
|
|
704
|
+
targetSocket.on('data', (chunk) => {
|
|
705
|
+
record.bytesSent += chunk.length;
|
|
706
|
+
this.timeoutManager.updateActivity(record);
|
|
707
|
+
});
|
|
708
|
+
// Wait for the outgoing connection to be ready before setting up piping
|
|
709
|
+
targetSocket.once('connect', () => {
|
|
710
|
+
// Clear the initial connection error handler
|
|
711
|
+
targetSocket.removeAllListeners('error');
|
|
712
|
+
// Add the normal error handler for established connections
|
|
713
|
+
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
714
|
+
// Process any remaining data in the queue before switching to piping
|
|
715
|
+
processDataQueue();
|
|
716
|
+
// Set up piping immediately
|
|
717
|
+
pipingEstablished = true;
|
|
718
|
+
// Flush all pending data to target
|
|
719
|
+
if (record.pendingData.length > 0) {
|
|
720
|
+
const combinedData = Buffer.concat(record.pendingData);
|
|
721
|
+
if (this.settings.enableDetailedLogging) {
|
|
722
|
+
console.log(`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`);
|
|
723
|
+
}
|
|
724
|
+
// Write pending data immediately
|
|
725
|
+
targetSocket.write(combinedData, (err) => {
|
|
726
|
+
if (err) {
|
|
727
|
+
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
|
|
728
|
+
return this.connectionManager.initiateCleanupOnce(record, 'write_error');
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
// Clear the buffer now that we've processed it
|
|
732
|
+
record.pendingData = [];
|
|
733
|
+
record.pendingDataSize = 0;
|
|
734
|
+
}
|
|
735
|
+
// Setup piping in both directions without any delays
|
|
736
|
+
socket.pipe(targetSocket);
|
|
737
|
+
targetSocket.pipe(socket);
|
|
738
|
+
// Resume the socket to ensure data flows
|
|
739
|
+
socket.resume();
|
|
740
|
+
// Process any data that might be queued in the interim
|
|
741
|
+
if (dataQueue.length > 0) {
|
|
742
|
+
// Write any remaining queued data directly to the target socket
|
|
743
|
+
for (const chunk of dataQueue) {
|
|
744
|
+
targetSocket.write(chunk);
|
|
745
|
+
}
|
|
746
|
+
// Clear the queue
|
|
747
|
+
dataQueue.length = 0;
|
|
748
|
+
queueSize = 0;
|
|
749
|
+
}
|
|
750
|
+
if (this.settings.enableDetailedLogging) {
|
|
751
|
+
console.log(`[${connectionId}] Connection established: ${record.remoteIP} -> ${finalTargetHost}:${connectionOptions.port}` +
|
|
752
|
+
`${serverName
|
|
753
|
+
? ` (SNI: ${serverName})`
|
|
754
|
+
: domainConfig
|
|
755
|
+
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
756
|
+
: ''}` +
|
|
757
|
+
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}`);
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
console.log(`Connection established: ${record.remoteIP} -> ${finalTargetHost}:${connectionOptions.port}` +
|
|
761
|
+
`${serverName
|
|
762
|
+
? ` (SNI: ${serverName})`
|
|
763
|
+
: domainConfig
|
|
764
|
+
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
765
|
+
: ''}`);
|
|
766
|
+
}
|
|
767
|
+
// Add the renegotiation handler for SNI validation
|
|
768
|
+
if (serverName) {
|
|
769
|
+
// Create connection info object for the existing connection
|
|
770
|
+
const connInfo = {
|
|
771
|
+
sourceIp: record.remoteIP,
|
|
772
|
+
sourcePort: record.incoming.remotePort || 0,
|
|
773
|
+
destIp: record.incoming.localAddress || '',
|
|
774
|
+
destPort: record.incoming.localPort || 0,
|
|
775
|
+
};
|
|
776
|
+
// Create a renegotiation handler function
|
|
777
|
+
const renegotiationHandler = this.tlsManager.createRenegotiationHandler(connectionId, serverName, connInfo, (connectionId, reason) => this.connectionManager.initiateCleanupOnce(record, reason));
|
|
778
|
+
// Store the handler in the connection record so we can remove it during cleanup
|
|
779
|
+
record.renegotiationHandler = renegotiationHandler;
|
|
780
|
+
// Add the handler to the socket
|
|
781
|
+
socket.on('data', renegotiationHandler);
|
|
782
|
+
if (this.settings.enableDetailedLogging) {
|
|
783
|
+
console.log(`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`);
|
|
784
|
+
if (this.settings.allowSessionTicket === false) {
|
|
785
|
+
console.log(`[${connectionId}] Session ticket usage is disabled. Connection will be reset on reconnection attempts.`);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// Set connection timeout
|
|
790
|
+
record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
|
|
791
|
+
console.log(`[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime, forcing cleanup.`);
|
|
792
|
+
this.connectionManager.initiateCleanupOnce(record, reason);
|
|
793
|
+
});
|
|
794
|
+
// Mark TLS handshake as complete for TLS connections
|
|
795
|
+
if (record.isTLS) {
|
|
796
|
+
record.tlsHandshakeComplete = true;
|
|
797
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
798
|
+
console.log(`[${connectionId}] TLS handshake complete for connection from ${record.remoteIP}`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtY29ubmVjdGlvbi1oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJveGllcy9zbWFydC1wcm94eS9yb3V0ZS1jb25uZWN0aW9uLWhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQU01QyxPQUFPLEVBQ0wsZUFBZSxFQUNmLGVBQWUsRUFDaEIsTUFBTSx3QkFBd0IsQ0FBQztBQUtoQyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDeEQsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDakUsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzlDLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBQy9ELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFJbEQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sc0JBQXNCO0lBR2pDLFlBQ0UsUUFBNEIsRUFDcEIsaUJBQW9DLEVBQ3BDLGVBQWdDLEVBQ2hDLG1CQUF3QyxFQUN4QyxVQUFzQixFQUN0QixrQkFBc0MsRUFDdEMsY0FBOEIsRUFDOUIsWUFBMEI7UUFOMUIsc0JBQWlCLEdBQWpCLGlCQUFpQixDQUFtQjtRQUNwQyxvQkFBZSxHQUFmLGVBQWUsQ0FBaUI7UUFDaEMsd0JBQW1CLEdBQW5CLG1CQUFtQixDQUFxQjtRQUN4QyxlQUFVLEdBQVYsVUFBVSxDQUFZO1FBQ3RCLHVCQUFrQixHQUFsQixrQkFBa0IsQ0FBb0I7UUFDdEMsbUJBQWMsR0FBZCxjQUFjLENBQWdCO1FBQzlCLGlCQUFZLEdBQVosWUFBWSxD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
|