@push.rocks/smartproxy 19.5.19 → 19.5.20
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 +0 -2
- package/dist_ts/core/utils/index.js +1 -3
- 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 +15 -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 +6 -2
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +48 -25
- 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 +189 -1
- package/readme.plan.md +621 -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 +0 -2
- 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 +15 -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 +8 -2
- package/ts/proxies/smart-proxy/route-connection-handler.ts +58 -30
- 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
|
@@ -5,6 +5,7 @@ import { TimeoutManager } from './timeout-manager.js';
|
|
|
5
5
|
import { logger } from '../../core/utils/logger.js';
|
|
6
6
|
import { LifecycleComponent } from '../../core/utils/lifecycle-component.js';
|
|
7
7
|
import { cleanupSocket } from '../../core/utils/socket-utils.js';
|
|
8
|
+
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Manages connection lifecycle, tracking, and cleanup with performance optimizations
|
|
@@ -53,8 +54,9 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
53
54
|
|
|
54
55
|
/**
|
|
55
56
|
* Create and track a new connection
|
|
57
|
+
* Accepts either a regular net.Socket or a WrappedSocket for transparent PROXY protocol support
|
|
56
58
|
*/
|
|
57
|
-
public createConnection(socket: plugins.net.Socket): IConnectionRecord | null {
|
|
59
|
+
public createConnection(socket: plugins.net.Socket | WrappedSocket): IConnectionRecord | null {
|
|
58
60
|
// Enforce connection limit
|
|
59
61
|
if (this.connectionRecords.size >= this.maxConnections) {
|
|
60
62
|
logger.log('warn', `Connection limit reached (${this.maxConnections}). Rejecting new connection.`, {
|
|
@@ -282,22 +284,26 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
282
284
|
const cleanupPromises: Promise<void>[] = [];
|
|
283
285
|
|
|
284
286
|
if (record.incoming) {
|
|
287
|
+
// Extract underlying socket if it's a WrappedSocket
|
|
288
|
+
const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
|
|
285
289
|
if (!record.incoming.writable || record.incoming.destroyed) {
|
|
286
290
|
// Socket is not active, clean up immediately
|
|
287
|
-
cleanupPromises.push(cleanupSocket(
|
|
291
|
+
cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { immediate: true }));
|
|
288
292
|
} else {
|
|
289
293
|
// Socket is still active, allow graceful cleanup
|
|
290
|
-
cleanupPromises.push(cleanupSocket(
|
|
294
|
+
cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
|
|
291
295
|
}
|
|
292
296
|
}
|
|
293
297
|
|
|
294
298
|
if (record.outgoing) {
|
|
299
|
+
// Extract underlying socket if it's a WrappedSocket
|
|
300
|
+
const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
|
|
295
301
|
if (!record.outgoing.writable || record.outgoing.destroyed) {
|
|
296
302
|
// Socket is not active, clean up immediately
|
|
297
|
-
cleanupPromises.push(cleanupSocket(
|
|
303
|
+
cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { immediate: true }));
|
|
298
304
|
} else {
|
|
299
305
|
// Socket is still active, allow graceful cleanup
|
|
300
|
-
cleanupPromises.push(cleanupSocket(
|
|
306
|
+
cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
309
|
|
|
@@ -570,11 +576,13 @@ export class ConnectionManager extends LifecycleComponent {
|
|
|
570
576
|
const shutdownPromises: Promise<void>[] = [];
|
|
571
577
|
|
|
572
578
|
if (record.incoming) {
|
|
573
|
-
|
|
579
|
+
const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
|
|
580
|
+
shutdownPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming-shutdown`, { immediate: true }));
|
|
574
581
|
}
|
|
575
582
|
|
|
576
583
|
if (record.outgoing) {
|
|
577
|
-
|
|
584
|
+
const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
|
|
585
|
+
shutdownPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing-shutdown`, { immediate: true }));
|
|
578
586
|
}
|
|
579
587
|
|
|
580
588
|
// Don't wait for shutdown cleanup in this batch processing
|
|
@@ -3,6 +3,7 @@ import { HttpProxy } from '../http-proxy/index.js';
|
|
|
3
3
|
import { setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
|
4
4
|
import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js';
|
|
5
5
|
import type { IRouteConfig } from './models/route-types.js';
|
|
6
|
+
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
|
|
6
7
|
|
|
7
8
|
export class HttpProxyBridge {
|
|
8
9
|
private httpProxy: HttpProxy | null = null;
|
|
@@ -98,7 +99,7 @@ export class HttpProxyBridge {
|
|
|
98
99
|
*/
|
|
99
100
|
public async forwardToHttpProxy(
|
|
100
101
|
connectionId: string,
|
|
101
|
-
socket: plugins.net.Socket,
|
|
102
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
102
103
|
record: IConnectionRecord,
|
|
103
104
|
initialChunk: Buffer,
|
|
104
105
|
httpProxyPort: number,
|
|
@@ -125,7 +126,10 @@ export class HttpProxyBridge {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
// Use centralized bidirectional forwarding
|
|
128
|
-
|
|
129
|
+
// Extract underlying socket if it's a WrappedSocket
|
|
130
|
+
const underlyingSocket = socket instanceof WrappedSocket ? socket.socket : socket;
|
|
131
|
+
|
|
132
|
+
setupBidirectionalForwarding(underlyingSocket, proxySocket, {
|
|
129
133
|
onClientData: (chunk) => {
|
|
130
134
|
// Update stats if needed
|
|
131
135
|
if (record) {
|
|
@@ -17,7 +17,7 @@ export { TlsManager } from './tls-manager.js';
|
|
|
17
17
|
export { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
18
18
|
|
|
19
19
|
// Export route-based components
|
|
20
|
-
export { RouteManager } from '
|
|
20
|
+
export { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
21
21
|
export { RouteConnectionHandler } from './route-connection-handler.js';
|
|
22
22
|
export { NFTablesManager } from './nftables-manager.js';
|
|
23
23
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../../../plugins.js';
|
|
2
|
+
import type { WrappedSocket } from '../../../core/models/wrapped-socket.js';
|
|
2
3
|
// Certificate types removed - define IAcmeOptions locally
|
|
3
4
|
export interface IAcmeOptions {
|
|
4
5
|
enabled?: boolean;
|
|
@@ -34,6 +35,11 @@ export interface ISmartProxyOptions {
|
|
|
34
35
|
// Port configuration
|
|
35
36
|
preserveSourceIP?: boolean; // Preserve client IP when forwarding
|
|
36
37
|
|
|
38
|
+
// PROXY protocol configuration
|
|
39
|
+
proxyIPs?: string[]; // List of trusted proxy IPs that can send PROXY protocol
|
|
40
|
+
acceptProxyProtocol?: boolean; // Global option to accept PROXY protocol (defaults based on proxyIPs)
|
|
41
|
+
sendProxyProtocol?: boolean; // Global option to send PROXY protocol to all targets
|
|
42
|
+
|
|
37
43
|
// Global/default settings
|
|
38
44
|
defaults?: {
|
|
39
45
|
target?: {
|
|
@@ -128,8 +134,8 @@ export interface ISmartProxyOptions {
|
|
|
128
134
|
*/
|
|
129
135
|
export interface IConnectionRecord {
|
|
130
136
|
id: string; // Unique connection identifier
|
|
131
|
-
incoming: plugins.net.Socket;
|
|
132
|
-
outgoing: plugins.net.Socket | null;
|
|
137
|
+
incoming: plugins.net.Socket | WrappedSocket;
|
|
138
|
+
outgoing: plugins.net.Socket | WrappedSocket | null;
|
|
133
139
|
incomingStartTime: number;
|
|
134
140
|
outgoingStartTime?: number;
|
|
135
141
|
outgoingClosedTime?: number;
|
|
@@ -2,14 +2,17 @@ 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';
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Handles new connection processing and setup logic with support for route-based configuration
|
|
@@ -80,39 +83,52 @@ export class RouteConnectionHandler {
|
|
|
80
83
|
const remoteIP = socket.remoteAddress || '';
|
|
81
84
|
const localPort = socket.localPort || 0;
|
|
82
85
|
|
|
86
|
+
// Always wrap the socket to prepare for potential PROXY protocol
|
|
87
|
+
const wrappedSocket = new WrappedSocket(socket);
|
|
88
|
+
|
|
89
|
+
// If this is from a trusted proxy, log it
|
|
90
|
+
if (this.settings.proxyIPs?.includes(remoteIP)) {
|
|
91
|
+
logger.log('debug', `Connection from trusted proxy ${remoteIP}, PROXY protocol parsing will be enabled`, {
|
|
92
|
+
remoteIP,
|
|
93
|
+
component: 'route-handler'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
// Validate IP against rate limits and connection limits
|
|
84
|
-
|
|
98
|
+
// Note: For wrapped sockets, this will use the underlying socket IP until PROXY protocol is parsed
|
|
99
|
+
const ipValidation = this.securityManager.validateIP(wrappedSocket.remoteAddress || '');
|
|
85
100
|
if (!ipValidation.allowed) {
|
|
86
|
-
logger.log('warn', `Connection rejected`, { remoteIP, reason: ipValidation.reason, component: 'route-handler' });
|
|
87
|
-
cleanupSocket(socket, `rejected-${ipValidation.reason}`, { immediate: true });
|
|
101
|
+
logger.log('warn', `Connection rejected`, { remoteIP: wrappedSocket.remoteAddress, reason: ipValidation.reason, component: 'route-handler' });
|
|
102
|
+
cleanupSocket(wrappedSocket.socket, `rejected-${ipValidation.reason}`, { immediate: true });
|
|
88
103
|
return;
|
|
89
104
|
}
|
|
90
105
|
|
|
91
|
-
// Create a new connection record
|
|
92
|
-
const record = this.connectionManager.createConnection(
|
|
106
|
+
// Create a new connection record with the wrapped socket
|
|
107
|
+
const record = this.connectionManager.createConnection(wrappedSocket);
|
|
93
108
|
if (!record) {
|
|
94
109
|
// Connection was rejected due to limit - socket already destroyed by connection manager
|
|
95
110
|
return;
|
|
96
111
|
}
|
|
97
112
|
const connectionId = record.id;
|
|
98
113
|
|
|
99
|
-
// Apply socket optimizations
|
|
100
|
-
socket
|
|
114
|
+
// Apply socket optimizations (apply to underlying socket)
|
|
115
|
+
const underlyingSocket = wrappedSocket.socket;
|
|
116
|
+
underlyingSocket.setNoDelay(this.settings.noDelay);
|
|
101
117
|
|
|
102
118
|
// Apply keep-alive settings if enabled
|
|
103
119
|
if (this.settings.keepAlive) {
|
|
104
|
-
|
|
120
|
+
underlyingSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
105
121
|
record.hasKeepAlive = true;
|
|
106
122
|
|
|
107
123
|
// Apply enhanced TCP keep-alive options if enabled
|
|
108
124
|
if (this.settings.enableKeepAliveProbes) {
|
|
109
125
|
try {
|
|
110
126
|
// These are platform-specific and may not be available
|
|
111
|
-
if ('setKeepAliveProbes' in
|
|
112
|
-
(
|
|
127
|
+
if ('setKeepAliveProbes' in underlyingSocket) {
|
|
128
|
+
(underlyingSocket as any).setKeepAliveProbes(10);
|
|
113
129
|
}
|
|
114
|
-
if ('setKeepAliveInterval' in
|
|
115
|
-
(
|
|
130
|
+
if ('setKeepAliveInterval' in underlyingSocket) {
|
|
131
|
+
(underlyingSocket as any).setKeepAliveInterval(1000);
|
|
116
132
|
}
|
|
117
133
|
} catch (err) {
|
|
118
134
|
// Ignore errors - these are optional enhancements
|
|
@@ -150,19 +166,19 @@ export class RouteConnectionHandler {
|
|
|
150
166
|
}
|
|
151
167
|
|
|
152
168
|
// Handle the connection - wait for initial data to determine if it's TLS
|
|
153
|
-
this.handleInitialData(
|
|
169
|
+
this.handleInitialData(wrappedSocket, record);
|
|
154
170
|
}
|
|
155
171
|
|
|
156
172
|
/**
|
|
157
173
|
* Handle initial data from a connection to determine routing
|
|
158
174
|
*/
|
|
159
|
-
private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
|
|
175
|
+
private handleInitialData(socket: plugins.net.Socket | WrappedSocket, record: IConnectionRecord): void {
|
|
160
176
|
const connectionId = record.id;
|
|
161
177
|
const localPort = record.localPort;
|
|
162
178
|
let initialDataReceived = false;
|
|
163
179
|
|
|
164
180
|
// Check if any routes on this port require TLS handling
|
|
165
|
-
const allRoutes = this.routeManager.
|
|
181
|
+
const allRoutes = this.routeManager.getRoutes();
|
|
166
182
|
const needsTlsHandling = allRoutes.some(route => {
|
|
167
183
|
// Check if route matches this port
|
|
168
184
|
const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
|
|
@@ -176,9 +192,11 @@ export class RouteConnectionHandler {
|
|
|
176
192
|
|
|
177
193
|
// If no routes require TLS handling and it's not port 443, route immediately
|
|
178
194
|
if (!needsTlsHandling && localPort !== 443) {
|
|
195
|
+
// Extract underlying socket for socket-utils functions
|
|
196
|
+
const underlyingSocket = getUnderlyingSocket(socket);
|
|
179
197
|
// Set up proper socket handlers for immediate routing
|
|
180
198
|
setupSocketHandlers(
|
|
181
|
-
|
|
199
|
+
underlyingSocket,
|
|
182
200
|
(reason) => {
|
|
183
201
|
// Only cleanup if connection hasn't been fully established
|
|
184
202
|
// Check if outgoing connection exists and is connected
|
|
@@ -370,7 +388,7 @@ export class RouteConnectionHandler {
|
|
|
370
388
|
* Route the connection based on match criteria
|
|
371
389
|
*/
|
|
372
390
|
private routeConnection(
|
|
373
|
-
socket: plugins.net.Socket,
|
|
391
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
374
392
|
record: IConnectionRecord,
|
|
375
393
|
serverName: string,
|
|
376
394
|
initialChunk?: Buffer
|
|
@@ -385,15 +403,21 @@ export class RouteConnectionHandler {
|
|
|
385
403
|
// For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
|
|
386
404
|
const skipDomainCheck = isHttpProxyPort && !record.isTLS;
|
|
387
405
|
|
|
388
|
-
//
|
|
389
|
-
const
|
|
406
|
+
// Create route context for matching
|
|
407
|
+
const routeContext: IRouteContext = {
|
|
390
408
|
port: localPort,
|
|
391
|
-
domain: serverName,
|
|
409
|
+
domain: skipDomainCheck ? undefined : serverName, // Skip domain if HTTP proxy without TLS
|
|
392
410
|
clientIp: remoteIP,
|
|
411
|
+
serverIp: socket.localAddress || '',
|
|
393
412
|
path: undefined, // We don't have path info at this point
|
|
413
|
+
isTls: record.isTLS,
|
|
394
414
|
tlsVersion: undefined, // We don't extract TLS version yet
|
|
395
|
-
|
|
396
|
-
|
|
415
|
+
timestamp: Date.now(),
|
|
416
|
+
connectionId: record.id
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Find matching route
|
|
420
|
+
const routeMatch = this.routeManager.findMatchingRoute(routeContext);
|
|
397
421
|
|
|
398
422
|
if (!routeMatch) {
|
|
399
423
|
logger.log('warn', `No route found for ${serverName || 'connection'} on port ${localPort} (connection: ${connectionId})`, {
|
|
@@ -552,7 +576,7 @@ export class RouteConnectionHandler {
|
|
|
552
576
|
* Handle a forward action for a route
|
|
553
577
|
*/
|
|
554
578
|
private handleForwardAction(
|
|
555
|
-
socket: plugins.net.Socket,
|
|
579
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
556
580
|
record: IConnectionRecord,
|
|
557
581
|
route: IRouteConfig,
|
|
558
582
|
initialChunk?: Buffer
|
|
@@ -869,7 +893,7 @@ export class RouteConnectionHandler {
|
|
|
869
893
|
* Handle a socket-handler action for a route
|
|
870
894
|
*/
|
|
871
895
|
private async handleSocketHandlerAction(
|
|
872
|
-
socket: plugins.net.Socket,
|
|
896
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
873
897
|
record: IConnectionRecord,
|
|
874
898
|
route: IRouteConfig,
|
|
875
899
|
initialChunk?: Buffer
|
|
@@ -933,8 +957,9 @@ export class RouteConnectionHandler {
|
|
|
933
957
|
});
|
|
934
958
|
|
|
935
959
|
try {
|
|
936
|
-
// Call the handler with socket
|
|
937
|
-
const
|
|
960
|
+
// Call the handler with the appropriate socket (extract underlying if needed)
|
|
961
|
+
const handlerSocket = getUnderlyingSocket(socket);
|
|
962
|
+
const result = route.action.socketHandler(handlerSocket, routeContext);
|
|
938
963
|
|
|
939
964
|
// Handle async handlers properly
|
|
940
965
|
if (result instanceof Promise) {
|
|
@@ -988,7 +1013,7 @@ export class RouteConnectionHandler {
|
|
|
988
1013
|
* Sets up a direct connection to the target
|
|
989
1014
|
*/
|
|
990
1015
|
private setupDirectConnection(
|
|
991
|
-
socket: plugins.net.Socket,
|
|
1016
|
+
socket: plugins.net.Socket | WrappedSocket,
|
|
992
1017
|
record: IConnectionRecord,
|
|
993
1018
|
serverName?: string,
|
|
994
1019
|
initialChunk?: Buffer,
|
|
@@ -1138,7 +1163,10 @@ export class RouteConnectionHandler {
|
|
|
1138
1163
|
}
|
|
1139
1164
|
|
|
1140
1165
|
// Use centralized bidirectional forwarding setup
|
|
1141
|
-
|
|
1166
|
+
// Extract underlying sockets for socket-utils functions
|
|
1167
|
+
const incomingSocket = getUnderlyingSocket(socket);
|
|
1168
|
+
|
|
1169
|
+
setupBidirectionalForwarding(incomingSocket, targetSocket, {
|
|
1142
1170
|
onClientData: (chunk) => {
|
|
1143
1171
|
record.bytesReceived += chunk.length;
|
|
1144
1172
|
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
|
/**
|