@push.rocks/smartproxy 19.5.19 → 19.5.21
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/core/models/index.d.ts +2 -0
- package/dist_ts/core/models/index.js +3 -1
- package/dist_ts/core/models/socket-types.d.ts +14 -0
- package/dist_ts/core/models/socket-types.js +15 -0
- package/dist_ts/core/models/wrapped-socket.d.ts +34 -0
- package/dist_ts/core/models/wrapped-socket.js +82 -0
- package/dist_ts/core/routing/index.d.ts +11 -0
- package/dist_ts/core/routing/index.js +17 -0
- package/dist_ts/core/routing/matchers/domain.d.ts +34 -0
- package/dist_ts/core/routing/matchers/domain.js +91 -0
- package/dist_ts/core/routing/matchers/header.d.ts +32 -0
- package/dist_ts/core/routing/matchers/header.js +94 -0
- package/dist_ts/core/routing/matchers/index.d.ts +18 -0
- package/dist_ts/core/routing/matchers/index.js +20 -0
- package/dist_ts/core/routing/matchers/ip.d.ts +53 -0
- package/dist_ts/core/routing/matchers/ip.js +169 -0
- package/dist_ts/core/routing/matchers/path.d.ts +44 -0
- package/dist_ts/core/routing/matchers/path.js +148 -0
- package/dist_ts/core/routing/route-manager.d.ts +88 -0
- package/dist_ts/core/routing/route-manager.js +342 -0
- package/dist_ts/core/routing/route-utils.d.ts +28 -0
- package/dist_ts/core/routing/route-utils.js +67 -0
- package/dist_ts/core/routing/specificity.d.ts +30 -0
- package/dist_ts/core/routing/specificity.js +115 -0
- package/dist_ts/core/routing/types.d.ts +41 -0
- package/dist_ts/core/routing/types.js +5 -0
- package/dist_ts/core/utils/index.d.ts +1 -2
- package/dist_ts/core/utils/index.js +2 -3
- package/dist_ts/core/utils/proxy-protocol.d.ts +45 -0
- package/dist_ts/core/utils/proxy-protocol.js +201 -0
- package/dist_ts/core/utils/route-manager.d.ts +0 -30
- package/dist_ts/core/utils/route-manager.js +6 -47
- package/dist_ts/core/utils/route-utils.d.ts +2 -68
- package/dist_ts/core/utils/route-utils.js +21 -218
- package/dist_ts/core/utils/security-utils.js +4 -4
- package/dist_ts/index.d.ts +2 -5
- package/dist_ts/index.js +5 -11
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +0 -1
- package/dist_ts/proxies/http-proxy/http-proxy.js +15 -60
- package/dist_ts/proxies/http-proxy/models/types.d.ts +0 -90
- package/dist_ts/proxies/http-proxy/models/types.js +1 -242
- package/dist_ts/proxies/http-proxy/request-handler.d.ts +3 -5
- package/dist_ts/proxies/http-proxy/request-handler.js +20 -171
- package/dist_ts/proxies/http-proxy/websocket-handler.d.ts +2 -5
- package/dist_ts/proxies/http-proxy/websocket-handler.js +15 -23
- package/dist_ts/proxies/index.d.ts +2 -2
- package/dist_ts/proxies/index.js +4 -3
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +3 -1
- package/dist_ts/proxies/smart-proxy/connection-manager.js +17 -7
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.d.ts +2 -1
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +5 -2
- package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/index.js +2 -2
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +7 -2
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +1 -0
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +155 -35
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +15 -4
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +10 -43
- package/dist_ts/routing/router/http-router.d.ts +89 -0
- package/dist_ts/routing/router/http-router.js +205 -0
- package/dist_ts/routing/router/index.d.ts +2 -5
- package/dist_ts/routing/router/index.js +3 -4
- package/package.json +1 -1
- package/readme.delete.md +187 -0
- package/readme.hints.md +196 -1
- package/readme.plan.md +625 -0
- package/readme.proxy-chain-summary.md +112 -0
- package/readme.proxy-protocol-example.md +462 -0
- package/readme.proxy-protocol.md +415 -0
- package/readme.routing.md +341 -0
- package/ts/core/models/index.ts +2 -0
- package/ts/core/models/socket-types.ts +21 -0
- package/ts/core/models/wrapped-socket.ts +99 -0
- package/ts/core/routing/index.ts +21 -0
- package/ts/core/routing/matchers/domain.ts +119 -0
- package/ts/core/routing/matchers/header.ts +120 -0
- package/ts/core/routing/matchers/index.ts +22 -0
- package/ts/core/routing/matchers/ip.ts +207 -0
- package/ts/core/routing/matchers/path.ts +184 -0
- package/ts/core/{utils → routing}/route-manager.ts +7 -57
- package/ts/core/routing/route-utils.ts +88 -0
- package/ts/core/routing/specificity.ts +141 -0
- package/ts/core/routing/types.ts +49 -0
- package/ts/core/utils/index.ts +1 -2
- package/ts/core/utils/proxy-protocol.ts +246 -0
- package/ts/core/utils/security-utils.ts +3 -7
- package/ts/index.ts +4 -14
- package/ts/proxies/http-proxy/http-proxy.ts +13 -68
- package/ts/proxies/http-proxy/models/types.ts +0 -324
- package/ts/proxies/http-proxy/request-handler.ts +15 -186
- package/ts/proxies/http-proxy/websocket-handler.ts +15 -26
- package/ts/proxies/index.ts +3 -2
- package/ts/proxies/smart-proxy/connection-manager.ts +17 -7
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +6 -2
- package/ts/proxies/smart-proxy/index.ts +1 -1
- package/ts/proxies/smart-proxy/models/interfaces.ts +9 -2
- package/ts/proxies/smart-proxy/models/route-types.ts +3 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +173 -42
- package/ts/proxies/smart-proxy/smart-proxy.ts +15 -3
- package/ts/proxies/smart-proxy/utils/route-utils.ts +11 -49
- package/ts/routing/router/http-router.ts +266 -0
- package/ts/routing/router/index.ts +3 -8
- package/readme.problems.md +0 -170
- package/ts/core/utils/route-utils.ts +0 -312
- package/ts/proxies/smart-proxy/route-manager.ts +0 -554
- package/ts/routing/router/proxy-router.ts +0 -437
- package/ts/routing/router/route-router.ts +0 -482
|
@@ -2,14 +2,18 @@ import * as plugins from '../../plugins.js';
|
|
|
2
2
|
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
|
|
3
3
|
import { logger } from '../../core/utils/logger.js';
|
|
4
4
|
// Route checking functions have been removed
|
|
5
|
-
import type { IRouteConfig, IRouteAction
|
|
5
|
+
import type { IRouteConfig, IRouteAction } from './models/route-types.js';
|
|
6
|
+
import type { IRouteContext } from '../../core/models/route-context.js';
|
|
6
7
|
import { ConnectionManager } from './connection-manager.js';
|
|
7
8
|
import { SecurityManager } from './security-manager.js';
|
|
8
9
|
import { TlsManager } from './tls-manager.js';
|
|
9
10
|
import { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
10
11
|
import { TimeoutManager } from './timeout-manager.js';
|
|
11
|
-
import { RouteManager } from '
|
|
12
|
+
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
12
13
|
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
14
|
+
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
15
|
+
import { getUnderlyingSocket } from '../../core/models/socket-types.js';
|
|
16
|
+
import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* Handles new connection processing and setup logic with support for route-based configuration
|
|
@@ -80,39 +84,52 @@ export class RouteConnectionHandler {
|
|
|
80
84
|
const remoteIP = socket.remoteAddress || '';
|
|
81
85
|
const localPort = socket.localPort || 0;
|
|
82
86
|
|
|
87
|
+
// Always wrap the socket to prepare for potential PROXY protocol
|
|
88
|
+
const wrappedSocket = new WrappedSocket(socket);
|
|
89
|
+
|
|
90
|
+
// If this is from a trusted proxy, log it
|
|
91
|
+
if (this.settings.proxyIPs?.includes(remoteIP)) {
|
|
92
|
+
logger.log('debug', `Connection from trusted proxy ${remoteIP}, PROXY protocol parsing will be enabled`, {
|
|
93
|
+
remoteIP,
|
|
94
|
+
component: 'route-handler'
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
83
98
|
// Validate IP against rate limits and connection limits
|
|
84
|
-
|
|
99
|
+
// Note: For wrapped sockets, this will use the underlying socket IP until PROXY protocol is parsed
|
|
100
|
+
const ipValidation = this.securityManager.validateIP(wrappedSocket.remoteAddress || '');
|
|
85
101
|
if (!ipValidation.allowed) {
|
|
86
|
-
logger.log('warn', `Connection rejected`, { remoteIP, reason: ipValidation.reason, component: 'route-handler' });
|
|
87
|
-
cleanupSocket(socket, `rejected-${ipValidation.reason}`, { immediate: true });
|
|
102
|
+
logger.log('warn', `Connection rejected`, { remoteIP: wrappedSocket.remoteAddress, reason: ipValidation.reason, component: 'route-handler' });
|
|
103
|
+
cleanupSocket(wrappedSocket.socket, `rejected-${ipValidation.reason}`, { immediate: true });
|
|
88
104
|
return;
|
|
89
105
|
}
|
|
90
106
|
|
|
91
|
-
// Create a new connection record
|
|
92
|
-
const record = this.connectionManager.createConnection(
|
|
107
|
+
// Create a new connection record with the wrapped socket
|
|
108
|
+
const record = this.connectionManager.createConnection(wrappedSocket);
|
|
93
109
|
if (!record) {
|
|
94
110
|
// Connection was rejected due to limit - socket already destroyed by connection manager
|
|
95
111
|
return;
|
|
96
112
|
}
|
|
97
113
|
const connectionId = record.id;
|
|
98
114
|
|
|
99
|
-
// Apply socket optimizations
|
|
100
|
-
socket
|
|
115
|
+
// Apply socket optimizations (apply to underlying socket)
|
|
116
|
+
const underlyingSocket = wrappedSocket.socket;
|
|
117
|
+
underlyingSocket.setNoDelay(this.settings.noDelay);
|
|
101
118
|
|
|
102
119
|
// Apply keep-alive settings if enabled
|
|
103
120
|
if (this.settings.keepAlive) {
|
|
104
|
-
|
|
121
|
+
underlyingSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
105
122
|
record.hasKeepAlive = true;
|
|
106
123
|
|
|
107
124
|
// Apply enhanced TCP keep-alive options if enabled
|
|
108
125
|
if (this.settings.enableKeepAliveProbes) {
|
|
109
126
|
try {
|
|
110
127
|
// These are platform-specific and may not be available
|
|
111
|
-
if ('setKeepAliveProbes' in
|
|
112
|
-
(
|
|
128
|
+
if ('setKeepAliveProbes' in underlyingSocket) {
|
|
129
|
+
(underlyingSocket as any).setKeepAliveProbes(10);
|
|
113
130
|
}
|
|
114
|
-
if ('setKeepAliveInterval' in
|
|
115
|
-
(
|
|
131
|
+
if ('setKeepAliveInterval' in underlyingSocket) {
|
|
132
|
+
(underlyingSocket as any).setKeepAliveInterval(1000);
|
|
116
133
|
}
|
|
117
134
|
} catch (err) {
|
|
118
135
|
// Ignore errors - these are optional enhancements
|
|
@@ -150,19 +167,19 @@ export class RouteConnectionHandler {
|
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
// Handle the connection - wait for initial data to determine if it's TLS
|
|
153
|
-
this.handleInitialData(
|
|
170
|
+
this.handleInitialData(wrappedSocket, record);
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
/**
|
|
157
174
|
* Handle initial data from a connection to determine routing
|
|
158
175
|
*/
|
|
159
|
-
private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
|
|
176
|
+
private handleInitialData(socket: plugins.net.Socket | WrappedSocket, record: IConnectionRecord): void {
|
|
160
177
|
const connectionId = record.id;
|
|
161
178
|
const localPort = record.localPort;
|
|
162
179
|
let initialDataReceived = false;
|
|
163
180
|
|
|
164
181
|
// Check if any routes on this port require TLS handling
|
|
165
|
-
const allRoutes = this.routeManager.
|
|
182
|
+
const allRoutes = this.routeManager.getRoutes();
|
|
166
183
|
const needsTlsHandling = allRoutes.some(route => {
|
|
167
184
|
// Check if route matches this port
|
|
168
185
|
const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
|
|
@@ -176,9 +193,11 @@ export class RouteConnectionHandler {
|
|
|
176
193
|
|
|
177
194
|
// If no routes require TLS handling and it's not port 443, route immediately
|
|
178
195
|
if (!needsTlsHandling && localPort !== 443) {
|
|
196
|
+
// Extract underlying socket for socket-utils functions
|
|
197
|
+
const underlyingSocket = getUnderlyingSocket(socket);
|
|
179
198
|
// Set up proper socket handlers for immediate routing
|
|
180
199
|
setupSocketHandlers(
|
|
181
|
-
|
|
200
|
+
underlyingSocket,
|
|
182
201
|
(reason) => {
|
|
183
202
|
// Only cleanup if connection hasn't been fully established
|
|
184
203
|
// Check if outgoing connection exists and is connected
|
|
@@ -277,17 +296,8 @@ export class RouteConnectionHandler {
|
|
|
277
296
|
}
|
|
278
297
|
});
|
|
279
298
|
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
// Clear the initial timeout since we've received data
|
|
283
|
-
if (initialTimeout) {
|
|
284
|
-
clearTimeout(initialTimeout);
|
|
285
|
-
initialTimeout = null;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
initialDataReceived = true;
|
|
289
|
-
record.hasReceivedInitialData = true;
|
|
290
|
-
|
|
299
|
+
// Handler for processing initial data (after potential PROXY protocol)
|
|
300
|
+
const processInitialData = (chunk: Buffer) => {
|
|
291
301
|
// Block non-TLS connections on port 443
|
|
292
302
|
if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) {
|
|
293
303
|
logger.log('warn', `Non-TLS connection ${connectionId} detected on port 443. Terminating connection - only TLS traffic is allowed on standard HTTPS port.`, {
|
|
@@ -363,6 +373,67 @@ export class RouteConnectionHandler {
|
|
|
363
373
|
|
|
364
374
|
// Find the appropriate route for this connection
|
|
365
375
|
this.routeConnection(socket, record, serverName, chunk);
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// First data handler to capture initial TLS handshake or PROXY protocol
|
|
379
|
+
socket.once('data', async (chunk: Buffer) => {
|
|
380
|
+
// Clear the initial timeout since we've received data
|
|
381
|
+
if (initialTimeout) {
|
|
382
|
+
clearTimeout(initialTimeout);
|
|
383
|
+
initialTimeout = null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
initialDataReceived = true;
|
|
387
|
+
record.hasReceivedInitialData = true;
|
|
388
|
+
|
|
389
|
+
// Check if this is from a trusted proxy and might have PROXY protocol
|
|
390
|
+
if (this.settings.proxyIPs?.includes(socket.remoteAddress || '') && this.settings.acceptProxyProtocol !== false) {
|
|
391
|
+
// Check if this starts with PROXY protocol
|
|
392
|
+
if (chunk.toString('ascii', 0, Math.min(6, chunk.length)).startsWith('PROXY ')) {
|
|
393
|
+
try {
|
|
394
|
+
const parseResult = ProxyProtocolParser.parse(chunk);
|
|
395
|
+
|
|
396
|
+
if (parseResult.proxyInfo) {
|
|
397
|
+
// Update the wrapped socket with real client info (if it's a WrappedSocket)
|
|
398
|
+
if (socket instanceof WrappedSocket) {
|
|
399
|
+
socket.setProxyInfo(parseResult.proxyInfo.sourceIP, parseResult.proxyInfo.sourcePort);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Update connection record with real client info
|
|
403
|
+
record.remoteIP = parseResult.proxyInfo.sourceIP;
|
|
404
|
+
record.remotePort = parseResult.proxyInfo.sourcePort;
|
|
405
|
+
|
|
406
|
+
logger.log('info', `PROXY protocol parsed successfully`, {
|
|
407
|
+
connectionId,
|
|
408
|
+
realClientIP: parseResult.proxyInfo.sourceIP,
|
|
409
|
+
realClientPort: parseResult.proxyInfo.sourcePort,
|
|
410
|
+
proxyIP: socket.remoteAddress,
|
|
411
|
+
component: 'route-handler'
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Process remaining data if any
|
|
415
|
+
if (parseResult.remainingData.length > 0) {
|
|
416
|
+
processInitialData(parseResult.remainingData);
|
|
417
|
+
} else {
|
|
418
|
+
// Wait for more data
|
|
419
|
+
socket.once('data', processInitialData);
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
} catch (error) {
|
|
424
|
+
logger.log('error', `Failed to parse PROXY protocol from trusted proxy`, {
|
|
425
|
+
connectionId,
|
|
426
|
+
error: error.message,
|
|
427
|
+
proxyIP: socket.remoteAddress,
|
|
428
|
+
component: 'route-handler'
|
|
429
|
+
});
|
|
430
|
+
// Continue processing as normal data
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Process as normal data (no PROXY protocol)
|
|
436
|
+
processInitialData(chunk);
|
|
366
437
|
});
|
|
367
438
|
}
|
|
368
439
|
|
|
@@ -370,7 +441,7 @@ export class RouteConnectionHandler {
|
|
|
370
441
|
* Route the connection based on match criteria
|
|
371
442
|
*/
|
|
372
443
|
private routeConnection(
|
|
373
|
-
socket: plugins.net.Socket,
|
|
444
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
374
445
|
record: IConnectionRecord,
|
|
375
446
|
serverName: string,
|
|
376
447
|
initialChunk?: Buffer
|
|
@@ -385,15 +456,21 @@ export class RouteConnectionHandler {
|
|
|
385
456
|
// For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
|
|
386
457
|
const skipDomainCheck = isHttpProxyPort && !record.isTLS;
|
|
387
458
|
|
|
388
|
-
//
|
|
389
|
-
const
|
|
459
|
+
// Create route context for matching
|
|
460
|
+
const routeContext: IRouteContext = {
|
|
390
461
|
port: localPort,
|
|
391
|
-
domain: serverName,
|
|
462
|
+
domain: skipDomainCheck ? undefined : serverName, // Skip domain if HTTP proxy without TLS
|
|
392
463
|
clientIp: remoteIP,
|
|
464
|
+
serverIp: socket.localAddress || '',
|
|
393
465
|
path: undefined, // We don't have path info at this point
|
|
466
|
+
isTls: record.isTLS,
|
|
394
467
|
tlsVersion: undefined, // We don't extract TLS version yet
|
|
395
|
-
|
|
396
|
-
|
|
468
|
+
timestamp: Date.now(),
|
|
469
|
+
connectionId: record.id
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// Find matching route
|
|
473
|
+
const routeMatch = this.routeManager.findMatchingRoute(routeContext);
|
|
397
474
|
|
|
398
475
|
if (!routeMatch) {
|
|
399
476
|
logger.log('warn', `No route found for ${serverName || 'connection'} on port ${localPort} (connection: ${connectionId})`, {
|
|
@@ -552,7 +629,7 @@ export class RouteConnectionHandler {
|
|
|
552
629
|
* Handle a forward action for a route
|
|
553
630
|
*/
|
|
554
631
|
private handleForwardAction(
|
|
555
|
-
socket: plugins.net.Socket,
|
|
632
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
556
633
|
record: IConnectionRecord,
|
|
557
634
|
route: IRouteConfig,
|
|
558
635
|
initialChunk?: Buffer
|
|
@@ -869,7 +946,7 @@ export class RouteConnectionHandler {
|
|
|
869
946
|
* Handle a socket-handler action for a route
|
|
870
947
|
*/
|
|
871
948
|
private async handleSocketHandlerAction(
|
|
872
|
-
socket: plugins.net.Socket,
|
|
949
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
873
950
|
record: IConnectionRecord,
|
|
874
951
|
route: IRouteConfig,
|
|
875
952
|
initialChunk?: Buffer
|
|
@@ -933,8 +1010,9 @@ export class RouteConnectionHandler {
|
|
|
933
1010
|
});
|
|
934
1011
|
|
|
935
1012
|
try {
|
|
936
|
-
// Call the handler with socket
|
|
937
|
-
const
|
|
1013
|
+
// Call the handler with the appropriate socket (extract underlying if needed)
|
|
1014
|
+
const handlerSocket = getUnderlyingSocket(socket);
|
|
1015
|
+
const result = route.action.socketHandler(handlerSocket, routeContext);
|
|
938
1016
|
|
|
939
1017
|
// Handle async handlers properly
|
|
940
1018
|
if (result instanceof Promise) {
|
|
@@ -988,7 +1066,7 @@ export class RouteConnectionHandler {
|
|
|
988
1066
|
* Sets up a direct connection to the target
|
|
989
1067
|
*/
|
|
990
1068
|
private setupDirectConnection(
|
|
991
|
-
socket: plugins.net.Socket,
|
|
1069
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
992
1070
|
record: IConnectionRecord,
|
|
993
1071
|
serverName?: string,
|
|
994
1072
|
initialChunk?: Buffer,
|
|
@@ -1094,7 +1172,7 @@ export class RouteConnectionHandler {
|
|
|
1094
1172
|
// Clean up the connection record - this is critical!
|
|
1095
1173
|
this.connectionManager.cleanupConnection(record, `connection_failed_${(error as any).code || 'unknown'}`);
|
|
1096
1174
|
},
|
|
1097
|
-
onConnect: () => {
|
|
1175
|
+
onConnect: async () => {
|
|
1098
1176
|
if (this.settings.enableDetailedLogging) {
|
|
1099
1177
|
logger.log('info', `Connection ${connectionId} established to target ${finalTargetHost}:${finalTargetPort}`, {
|
|
1100
1178
|
connectionId,
|
|
@@ -1110,6 +1188,56 @@ export class RouteConnectionHandler {
|
|
|
1110
1188
|
// Add the normal error handler for established connections
|
|
1111
1189
|
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
|
1112
1190
|
|
|
1191
|
+
// Check if we should send PROXY protocol header
|
|
1192
|
+
const shouldSendProxyProtocol = record.routeConfig?.action?.sendProxyProtocol ||
|
|
1193
|
+
this.settings.sendProxyProtocol;
|
|
1194
|
+
|
|
1195
|
+
if (shouldSendProxyProtocol) {
|
|
1196
|
+
try {
|
|
1197
|
+
// Generate PROXY protocol header
|
|
1198
|
+
const proxyInfo = {
|
|
1199
|
+
protocol: (record.remoteIP.includes(':') ? 'TCP6' : 'TCP4') as 'TCP4' | 'TCP6',
|
|
1200
|
+
sourceIP: record.remoteIP,
|
|
1201
|
+
sourcePort: record.remotePort || socket.remotePort || 0,
|
|
1202
|
+
destinationIP: socket.localAddress || '',
|
|
1203
|
+
destinationPort: socket.localPort || 0
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
const proxyHeader = ProxyProtocolParser.generate(proxyInfo);
|
|
1207
|
+
|
|
1208
|
+
// Send PROXY protocol header first
|
|
1209
|
+
await new Promise<void>((resolve, reject) => {
|
|
1210
|
+
targetSocket.write(proxyHeader, (err) => {
|
|
1211
|
+
if (err) {
|
|
1212
|
+
logger.log('error', `Failed to send PROXY protocol header`, {
|
|
1213
|
+
connectionId,
|
|
1214
|
+
error: err.message,
|
|
1215
|
+
component: 'route-handler'
|
|
1216
|
+
});
|
|
1217
|
+
reject(err);
|
|
1218
|
+
} else {
|
|
1219
|
+
logger.log('info', `PROXY protocol header sent to backend`, {
|
|
1220
|
+
connectionId,
|
|
1221
|
+
targetHost: finalTargetHost,
|
|
1222
|
+
targetPort: finalTargetPort,
|
|
1223
|
+
sourceIP: proxyInfo.sourceIP,
|
|
1224
|
+
sourcePort: proxyInfo.sourcePort,
|
|
1225
|
+
component: 'route-handler'
|
|
1226
|
+
});
|
|
1227
|
+
resolve();
|
|
1228
|
+
}
|
|
1229
|
+
});
|
|
1230
|
+
});
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
logger.log('error', `Error sending PROXY protocol header`, {
|
|
1233
|
+
connectionId,
|
|
1234
|
+
error: error.message,
|
|
1235
|
+
component: 'route-handler'
|
|
1236
|
+
});
|
|
1237
|
+
// Continue anyway - don't break the connection
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1113
1241
|
// Flush any pending data to target
|
|
1114
1242
|
if (record.pendingData.length > 0) {
|
|
1115
1243
|
const combinedData = Buffer.concat(record.pendingData);
|
|
@@ -1138,7 +1266,10 @@ export class RouteConnectionHandler {
|
|
|
1138
1266
|
}
|
|
1139
1267
|
|
|
1140
1268
|
// Use centralized bidirectional forwarding setup
|
|
1141
|
-
|
|
1269
|
+
// Extract underlying sockets for socket-utils functions
|
|
1270
|
+
const incomingSocket = getUnderlyingSocket(socket);
|
|
1271
|
+
|
|
1272
|
+
setupBidirectionalForwarding(incomingSocket, targetSocket, {
|
|
1142
1273
|
onClientData: (chunk) => {
|
|
1143
1274
|
record.bytesReceived += chunk.length;
|
|
1144
1275
|
this.timeoutManager.updateActivity(record);
|
|
@@ -8,7 +8,7 @@ import { TlsManager } from './tls-manager.js';
|
|
|
8
8
|
import { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
9
9
|
import { TimeoutManager } from './timeout-manager.js';
|
|
10
10
|
import { PortManager } from './port-manager.js';
|
|
11
|
-
import { RouteManager } from '
|
|
11
|
+
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
12
12
|
import { RouteConnectionHandler } from './route-connection-handler.js';
|
|
13
13
|
import { NFTablesManager } from './nftables-manager.js';
|
|
14
14
|
|
|
@@ -162,8 +162,20 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
162
162
|
this.timeoutManager
|
|
163
163
|
);
|
|
164
164
|
|
|
165
|
-
// Create the route manager
|
|
166
|
-
|
|
165
|
+
// Create the route manager with SharedRouteManager API
|
|
166
|
+
// Create a logger adapter to match ILogger interface
|
|
167
|
+
const loggerAdapter = {
|
|
168
|
+
debug: (message: string, data?: any) => logger.log('debug', message, data),
|
|
169
|
+
info: (message: string, data?: any) => logger.log('info', message, data),
|
|
170
|
+
warn: (message: string, data?: any) => logger.log('warn', message, data),
|
|
171
|
+
error: (message: string, data?: any) => logger.log('error', message, data)
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
this.routeManager = new RouteManager({
|
|
175
|
+
logger: loggerAdapter,
|
|
176
|
+
enableDetailedLogging: this.settings.enableDetailedLogging,
|
|
177
|
+
routes: this.settings.routes
|
|
178
|
+
});
|
|
167
179
|
|
|
168
180
|
|
|
169
181
|
// Create other required components
|
|
@@ -92,6 +92,8 @@ export function mergeRouteConfigs(
|
|
|
92
92
|
return mergedRoute;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
import { DomainMatcher, PathMatcher, HeaderMatcher } from '../../../core/routing/matchers/index.js';
|
|
96
|
+
|
|
95
97
|
/**
|
|
96
98
|
* Check if a route matches a domain
|
|
97
99
|
* @param route The route to check
|
|
@@ -107,14 +109,7 @@ export function routeMatchesDomain(route: IRouteConfig, domain: string): boolean
|
|
|
107
109
|
? route.match.domains
|
|
108
110
|
: [route.match.domains];
|
|
109
111
|
|
|
110
|
-
return domains.some(d =>
|
|
111
|
-
// Handle wildcard domains
|
|
112
|
-
if (d.startsWith('*.')) {
|
|
113
|
-
const suffix = d.substring(2);
|
|
114
|
-
return domain.endsWith(suffix) && domain.split('.').length > suffix.split('.').length;
|
|
115
|
-
}
|
|
116
|
-
return d.toLowerCase() === domain.toLowerCase();
|
|
117
|
-
});
|
|
112
|
+
return domains.some(d => DomainMatcher.match(d, domain));
|
|
118
113
|
}
|
|
119
114
|
|
|
120
115
|
/**
|
|
@@ -160,28 +155,7 @@ export function routeMatchesPath(route: IRouteConfig, path: string): boolean {
|
|
|
160
155
|
return true; // No path specified means it matches any path
|
|
161
156
|
}
|
|
162
157
|
|
|
163
|
-
|
|
164
|
-
if (route.match.path === path) {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Handle path prefix with trailing slash (e.g., /api/)
|
|
169
|
-
if (route.match.path.endsWith('/') && path.startsWith(route.match.path)) {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Handle exact path match without trailing slash
|
|
174
|
-
if (!route.match.path.endsWith('/') && path === route.match.path) {
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Handle wildcard paths (e.g., /api/*)
|
|
179
|
-
if (route.match.path.endsWith('*')) {
|
|
180
|
-
const prefix = route.match.path.slice(0, -1);
|
|
181
|
-
return path.startsWith(prefix);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return false;
|
|
158
|
+
return PathMatcher.match(route.match.path, path).matches;
|
|
185
159
|
}
|
|
186
160
|
|
|
187
161
|
/**
|
|
@@ -198,25 +172,13 @@ export function routeMatchesHeaders(
|
|
|
198
172
|
return true; // No headers specified means it matches any headers
|
|
199
173
|
}
|
|
200
174
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Handle exact match
|
|
209
|
-
if (typeof value === 'string') {
|
|
210
|
-
return headers[key] === value;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Handle regex match
|
|
214
|
-
if (value instanceof RegExp) {
|
|
215
|
-
return value.test(headers[key]);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return false;
|
|
219
|
-
});
|
|
175
|
+
// Convert RegExp patterns to strings for HeaderMatcher
|
|
176
|
+
const stringHeaders: Record<string, string> = {};
|
|
177
|
+
for (const [key, value] of Object.entries(route.match.headers)) {
|
|
178
|
+
stringHeaders[key] = value instanceof RegExp ? value.source : value;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return HeaderMatcher.matchAll(stringHeaders, headers);
|
|
220
182
|
}
|
|
221
183
|
|
|
222
184
|
/**
|