@push.rocks/smartproxy 19.4.2 → 19.5.3
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 +1 -1
- package/dist_ts/proxies/http-proxy/handlers/index.d.ts +1 -2
- package/dist_ts/proxies/http-proxy/handlers/index.js +3 -3
- package/dist_ts/proxies/http-proxy/models/types.js +2 -2
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +30 -25
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +2 -5
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +9 -41
- package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
- package/dist_ts/proxies/smart-proxy/nftables-manager.js +5 -6
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -12
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +152 -47
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +2 -0
- package/dist_ts/proxies/smart-proxy/route-manager.js +7 -8
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +2 -2
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -3
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +61 -20
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +249 -53
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -18
- package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +4 -43
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +14 -15
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +10 -31
- package/package.json +7 -7
- package/readme.hints.md +168 -5
- package/readme.plan.md +314 -382
- package/readme.plan2.md +764 -0
- package/readme.problems.md +86 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/http-proxy/handlers/index.ts +1 -2
- package/ts/proxies/http-proxy/models/types.ts +1 -1
- package/ts/proxies/smart-proxy/certificate-manager.ts +29 -23
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +1 -4
- package/ts/proxies/smart-proxy/models/route-types.ts +12 -59
- package/ts/proxies/smart-proxy/nftables-manager.ts +4 -5
- package/ts/proxies/smart-proxy/route-connection-handler.ts +170 -64
- package/ts/proxies/smart-proxy/route-manager.ts +7 -8
- package/ts/proxies/smart-proxy/utils/index.ts +0 -2
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +289 -70
- package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -56
- package/ts/proxies/smart-proxy/utils/route-utils.ts +12 -15
- package/ts/proxies/smart-proxy/utils/route-validators.ts +9 -31
- package/ts/proxies/http-proxy/handlers/redirect-handler.ts +0 -105
- package/ts/proxies/http-proxy/handlers/static-handler.ts +0 -261
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# SmartProxy Module Problems
|
|
2
|
+
|
|
3
|
+
Based on test analysis, the following potential issues have been identified in the SmartProxy module:
|
|
4
|
+
|
|
5
|
+
## 1. HttpProxy Route Configuration Issue
|
|
6
|
+
**Location**: `ts/proxies/http-proxy/http-proxy.ts:380`
|
|
7
|
+
**Problem**: The HttpProxy is trying to read the 'type' property of an undefined object when updating route configurations.
|
|
8
|
+
**Evidence**: `test.http-forwarding-fix.ts` fails with:
|
|
9
|
+
```
|
|
10
|
+
TypeError: Cannot read properties of undefined (reading 'type')
|
|
11
|
+
at HttpProxy.updateRouteConfigs (/mnt/data/lossless/push.rocks/smartproxy/ts/proxies/http-proxy/http-proxy.ts:380:24)
|
|
12
|
+
```
|
|
13
|
+
**Impact**: Routes with `useHttpProxy` configuration may not work properly.
|
|
14
|
+
|
|
15
|
+
## 2. Connection Forwarding Issues
|
|
16
|
+
**Problem**: Basic TCP forwarding appears to not be working correctly after the simplification to just 'forward' and 'socket-handler' action types.
|
|
17
|
+
**Evidence**: Multiple forwarding tests timeout waiting for data to be forwarded:
|
|
18
|
+
- `test.forwarding-fix-verification.ts` - times out waiting for forwarded data
|
|
19
|
+
- `test.connection-forwarding.ts` - times out on SNI-based forwarding
|
|
20
|
+
**Impact**: The 'forward' action type may not be properly forwarding connections to target servers.
|
|
21
|
+
|
|
22
|
+
## 3. Missing Certificate Manager Methods
|
|
23
|
+
**Problem**: Tests expect `provisionAllCertificates` method on certificate manager but it may not exist or may not be properly initialized.
|
|
24
|
+
**Evidence**: Multiple tests fail with "this.certManager.provisionAllCertificates is not a function"
|
|
25
|
+
**Impact**: Certificate provisioning may not work as expected.
|
|
26
|
+
|
|
27
|
+
## 4. Route Update Mechanism
|
|
28
|
+
**Problem**: The route update mechanism may have issues preserving certificate manager callbacks and other state.
|
|
29
|
+
**Evidence**: Tests specifically designed to verify callback preservation after route updates.
|
|
30
|
+
**Impact**: Dynamic route updates might break certificate management functionality.
|
|
31
|
+
|
|
32
|
+
## 5. Route-Specific Security Not Fully Implemented
|
|
33
|
+
**Problem**: While the route definitions support security configurations (ipAllowList, ipBlockList, authentication), these are not being enforced at the route level.
|
|
34
|
+
**Evidence**:
|
|
35
|
+
- SecurityManager has methods like `isIPAuthorized` for route-specific security
|
|
36
|
+
- Route connection handler only checks global IP validation, not route-specific security rules
|
|
37
|
+
- No evidence of route.action.security being checked when handling connections
|
|
38
|
+
**Impact**: Route-specific security rules defined in configuration are not enforced, potentially allowing unauthorized access.
|
|
39
|
+
**Status**: ✅ FIXED - Route-specific IP allow/block lists are now enforced when a route is matched. Authentication is logged as not enforceable for non-terminated connections.
|
|
40
|
+
**Additional Fix**: Removed security checks from route matching logic - security is now properly enforced AFTER a route is matched, not during matching.
|
|
41
|
+
|
|
42
|
+
## 6. Security Property Location Consolidation
|
|
43
|
+
**Problem**: Security was defined in two places - route.security and route.action.security - causing confusion.
|
|
44
|
+
**Status**: ✅ FIXED - Consolidated to only route.security. Removed action.security from types and updated all references.
|
|
45
|
+
|
|
46
|
+
## Recommendations
|
|
47
|
+
|
|
48
|
+
1. **Verify Forward Action Implementation**: Check that the 'forward' action type properly establishes bidirectional data flow between client and target server. ✅ FIXED - Basic forwarding now works correctly.
|
|
49
|
+
|
|
50
|
+
2. **Fix HttpProxy Route Handling**: Ensure that route objects passed to HttpProxy.updateRouteConfigs have the expected structure with all required properties. ✅ FIXED - Routes now preserve their structure.
|
|
51
|
+
|
|
52
|
+
3. **Review Certificate Manager API**: Ensure all expected methods exist and are properly documented.
|
|
53
|
+
|
|
54
|
+
4. **Add Integration Tests**: Many unit tests are testing internal implementation details. Consider adding more integration tests that test the public API.
|
|
55
|
+
|
|
56
|
+
5. **Implement Route-Specific Security**: Add security checks when a route is matched to enforce route-specific IP allow/block lists and authentication rules. ✅ FIXED - IP allow/block lists are now enforced at the route level.
|
|
57
|
+
|
|
58
|
+
6. **Fix TLS Detection Logic**: The connection handler was treating all connections as TLS. This has been partially fixed but needs proper testing for all TLS modes.
|
|
59
|
+
|
|
60
|
+
## 7. HTTP Domain Matching Issue
|
|
61
|
+
**Problem**: Routes with domain restrictions fail to match HTTP connections because domain information is only available after HTTP headers are received, but route matching happens immediately upon connection.
|
|
62
|
+
**Evidence**:
|
|
63
|
+
- `test.http-port8080-forwarding.ts` - "No route found for connection on port 8080" despite having a matching route
|
|
64
|
+
- HTTP connections provide domain info via the Host header, which arrives after the initial TCP connection
|
|
65
|
+
- Route matching in `handleInitialData` happens before HTTP headers are parsed
|
|
66
|
+
**Impact**: HTTP routes with domain restrictions cannot be matched, forcing users to remove domain restrictions for HTTP routes.
|
|
67
|
+
**Root Cause**: For non-TLS connections, SmartProxy attempts to match routes immediately, but the domain information needed for matching is only available after parsing HTTP headers.
|
|
68
|
+
**Status**: ✅ FIXED - Added skipDomainCheck parameter to route matching for HTTP proxy ports. When a port is configured with useHttpProxy and the connection is not TLS, domain validation is skipped at the initial route matching stage, allowing the HttpProxy to handle domain-based routing after headers are received.
|
|
69
|
+
|
|
70
|
+
## 8. HttpProxy Plain HTTP Forwarding Issue
|
|
71
|
+
**Problem**: HttpProxy is an HTTPS server but SmartProxy forwards plain HTTP connections to it via `useHttpProxy` configuration.
|
|
72
|
+
**Evidence**:
|
|
73
|
+
- `test.http-port8080-forwarding.ts` - Connection immediately closed after forwarding to HttpProxy
|
|
74
|
+
- HttpProxy is created with `http2.createSecureServer` expecting TLS connections
|
|
75
|
+
- SmartProxy forwards raw HTTP data to HttpProxy's HTTPS port
|
|
76
|
+
**Impact**: Plain HTTP connections cannot be handled by HttpProxy, despite `useHttpProxy` configuration suggesting this should work.
|
|
77
|
+
**Root Cause**: Design mismatch - HttpProxy is designed for HTTPS/TLS termination, not plain HTTP forwarding.
|
|
78
|
+
**Status**: Documented. The `useHttpProxy` configuration should only be used for ports that receive TLS connections requiring termination. For plain HTTP forwarding, use direct forwarding without HttpProxy.
|
|
79
|
+
|
|
80
|
+
## 9. Route Security Configuration Location Issue
|
|
81
|
+
**Problem**: Tests were placing security configuration in `route.action.security` instead of `route.security`.
|
|
82
|
+
**Evidence**:
|
|
83
|
+
- `test.route-security.ts` - IP block list test failing because security was in wrong location
|
|
84
|
+
- IRouteConfig interface defines security at route level, not inside action
|
|
85
|
+
**Impact**: Security rules defined in action.security were ignored, causing tests to fail.
|
|
86
|
+
**Status**: ✅ FIXED - Updated tests to place security configuration at the correct location (route.security).
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '19.
|
|
6
|
+
version: '19.5.3',
|
|
7
7
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
|
8
8
|
}
|
|
@@ -153,7 +153,7 @@ export function convertLegacyConfigToRouteConfig(
|
|
|
153
153
|
|
|
154
154
|
// Add authentication if present
|
|
155
155
|
if (legacyConfig.authentication) {
|
|
156
|
-
routeConfig.
|
|
156
|
+
routeConfig.security = {
|
|
157
157
|
authentication: {
|
|
158
158
|
type: 'basic',
|
|
159
159
|
credentials: [{
|
|
@@ -5,6 +5,7 @@ import type { IAcmeOptions } from './models/interfaces.js';
|
|
|
5
5
|
import { CertStore } from './cert-store.js';
|
|
6
6
|
import type { AcmeStateManager } from './acme-state-manager.js';
|
|
7
7
|
import { logger } from '../../core/utils/logger.js';
|
|
8
|
+
import { SocketHandlers } from './utils/route-helpers.js';
|
|
8
9
|
|
|
9
10
|
export interface ICertStatus {
|
|
10
11
|
domain: string;
|
|
@@ -693,22 +694,24 @@ export class SmartCertManager {
|
|
|
693
694
|
path: '/.well-known/acme-challenge/*'
|
|
694
695
|
},
|
|
695
696
|
action: {
|
|
696
|
-
type: '
|
|
697
|
-
|
|
697
|
+
type: 'socket-handler',
|
|
698
|
+
socketHandler: SocketHandlers.httpServer((req, res) => {
|
|
698
699
|
// Extract the token from the path
|
|
699
|
-
const token =
|
|
700
|
+
const token = req.url?.split('/').pop();
|
|
700
701
|
if (!token) {
|
|
701
|
-
|
|
702
|
+
res.status(404);
|
|
703
|
+
res.send('Not found');
|
|
704
|
+
return;
|
|
702
705
|
}
|
|
703
706
|
|
|
704
707
|
// Create mock request/response objects for SmartAcme
|
|
708
|
+
let responseData: any = null;
|
|
705
709
|
const mockReq = {
|
|
706
|
-
url:
|
|
707
|
-
method:
|
|
708
|
-
headers:
|
|
710
|
+
url: req.url,
|
|
711
|
+
method: req.method,
|
|
712
|
+
headers: req.headers
|
|
709
713
|
};
|
|
710
714
|
|
|
711
|
-
let responseData: any = null;
|
|
712
715
|
const mockRes = {
|
|
713
716
|
statusCode: 200,
|
|
714
717
|
setHeader: (name: string, value: string) => {},
|
|
@@ -718,24 +721,27 @@ export class SmartCertManager {
|
|
|
718
721
|
};
|
|
719
722
|
|
|
720
723
|
// Use SmartAcme's handler
|
|
721
|
-
const
|
|
724
|
+
const handleAcme = () => {
|
|
722
725
|
http01Handler.handleRequest(mockReq as any, mockRes as any, () => {
|
|
723
|
-
|
|
726
|
+
// Not handled by ACME
|
|
727
|
+
res.status(404);
|
|
728
|
+
res.send('Not found');
|
|
724
729
|
});
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
730
|
+
|
|
731
|
+
// Give it a moment to process, then send response
|
|
732
|
+
setTimeout(() => {
|
|
733
|
+
if (responseData) {
|
|
734
|
+
res.header('Content-Type', 'text/plain');
|
|
735
|
+
res.send(String(responseData));
|
|
736
|
+
} else {
|
|
737
|
+
res.status(404);
|
|
738
|
+
res.send('Not found');
|
|
739
|
+
}
|
|
740
|
+
}, 100);
|
|
741
|
+
};
|
|
728
742
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
status: mockRes.statusCode,
|
|
732
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
733
|
-
body: responseData
|
|
734
|
-
};
|
|
735
|
-
} else {
|
|
736
|
-
return { status: 404, body: 'Not found' };
|
|
737
|
-
}
|
|
738
|
-
}
|
|
743
|
+
handleAcme();
|
|
744
|
+
})
|
|
739
745
|
}
|
|
740
746
|
};
|
|
741
747
|
|
|
@@ -73,10 +73,7 @@ export class HttpProxyBridge {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
return {
|
|
76
|
-
|
|
77
|
-
target: route.action.target,
|
|
78
|
-
tls: route.action.tls,
|
|
79
|
-
security: route.action.security,
|
|
76
|
+
...route, // Keep the original route structure
|
|
80
77
|
match: {
|
|
81
78
|
...route.match,
|
|
82
79
|
domains: domain // Ensure domains is always set for HttpProxy
|
|
@@ -2,11 +2,20 @@ import * as plugins from '../../../plugins.js';
|
|
|
2
2
|
// Certificate types removed - use local definition
|
|
3
3
|
import type { TForwardingType } from '../../../forwarding/config/forwarding-types.js';
|
|
4
4
|
import type { PortRange } from '../../../proxies/nftables-proxy/models/interfaces.js';
|
|
5
|
+
import type { IRouteContext } from '../../../core/models/route-context.js';
|
|
6
|
+
|
|
7
|
+
// Re-export IRouteContext for convenience
|
|
8
|
+
export type { IRouteContext };
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Supported action types for route configurations
|
|
8
12
|
*/
|
|
9
|
-
export type TRouteActionType = 'forward' | '
|
|
13
|
+
export type TRouteActionType = 'forward' | 'socket-handler';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Socket handler function type
|
|
17
|
+
*/
|
|
18
|
+
export type TSocketHandler = (socket: plugins.net.Socket, context: IRouteContext) => void | Promise<void>;
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
21
|
* TLS handling modes for route configurations
|
|
@@ -35,36 +44,6 @@ export interface IRouteMatch {
|
|
|
35
44
|
headers?: Record<string, string | RegExp>; // Match specific HTTP headers
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
/**
|
|
39
|
-
* Context provided to port and host mapping functions
|
|
40
|
-
*/
|
|
41
|
-
export interface IRouteContext {
|
|
42
|
-
// Connection information
|
|
43
|
-
port: number; // The matched incoming port
|
|
44
|
-
domain?: string; // The domain from SNI or Host header
|
|
45
|
-
clientIp: string; // The client's IP address
|
|
46
|
-
serverIp: string; // The server's IP address
|
|
47
|
-
path?: string; // URL path (for HTTP connections)
|
|
48
|
-
query?: string; // Query string (for HTTP connections)
|
|
49
|
-
headers?: Record<string, string>; // HTTP headers (for HTTP connections)
|
|
50
|
-
method?: string; // HTTP method (for HTTP connections)
|
|
51
|
-
|
|
52
|
-
// TLS information
|
|
53
|
-
isTls: boolean; // Whether the connection is TLS
|
|
54
|
-
tlsVersion?: string; // TLS version if applicable
|
|
55
|
-
|
|
56
|
-
// Route information
|
|
57
|
-
routeName?: string; // The name of the matched route
|
|
58
|
-
routeId?: string; // The ID of the matched route
|
|
59
|
-
|
|
60
|
-
// Target information (resolved from dynamic mapping)
|
|
61
|
-
targetHost?: string | string[]; // The resolved target host(s)
|
|
62
|
-
targetPort?: number; // The resolved target port
|
|
63
|
-
|
|
64
|
-
// Additional properties
|
|
65
|
-
timestamp: number; // The request timestamp
|
|
66
|
-
connectionId: string; // Unique connection identifier
|
|
67
|
-
}
|
|
68
47
|
|
|
69
48
|
/**
|
|
70
49
|
* Target configuration for forwarding
|
|
@@ -84,15 +63,6 @@ export interface IRouteAcme {
|
|
|
84
63
|
renewBeforeDays?: number; // Days before expiry to renew (default: 30)
|
|
85
64
|
}
|
|
86
65
|
|
|
87
|
-
/**
|
|
88
|
-
* Static route handler response
|
|
89
|
-
*/
|
|
90
|
-
export interface IStaticResponse {
|
|
91
|
-
status: number;
|
|
92
|
-
headers?: Record<string, string>;
|
|
93
|
-
body: string | Buffer;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
66
|
/**
|
|
97
67
|
* TLS configuration for route actions
|
|
98
68
|
*/
|
|
@@ -112,14 +82,6 @@ export interface IRouteTls {
|
|
|
112
82
|
sessionTimeout?: number; // TLS session timeout in seconds
|
|
113
83
|
}
|
|
114
84
|
|
|
115
|
-
/**
|
|
116
|
-
* Redirect configuration for route actions
|
|
117
|
-
*/
|
|
118
|
-
export interface IRouteRedirect {
|
|
119
|
-
to: string; // URL or template with {domain}, {port}, etc.
|
|
120
|
-
status: 301 | 302 | 307 | 308;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
85
|
/**
|
|
124
86
|
* Authentication options
|
|
125
87
|
*/
|
|
@@ -265,21 +227,12 @@ export interface IRouteAction {
|
|
|
265
227
|
// TLS handling
|
|
266
228
|
tls?: IRouteTls;
|
|
267
229
|
|
|
268
|
-
// For redirects
|
|
269
|
-
redirect?: IRouteRedirect;
|
|
270
|
-
|
|
271
|
-
// For static files
|
|
272
|
-
static?: IRouteStaticFiles;
|
|
273
|
-
|
|
274
230
|
// WebSocket support
|
|
275
231
|
websocket?: IRouteWebSocket;
|
|
276
232
|
|
|
277
233
|
// Load balancing options
|
|
278
234
|
loadBalancing?: IRouteLoadBalancing;
|
|
279
235
|
|
|
280
|
-
// Security options
|
|
281
|
-
security?: IRouteSecurity;
|
|
282
|
-
|
|
283
236
|
// Advanced options
|
|
284
237
|
advanced?: IRouteAdvanced;
|
|
285
238
|
|
|
@@ -295,8 +248,8 @@ export interface IRouteAction {
|
|
|
295
248
|
// NFTables-specific options
|
|
296
249
|
nftables?: INfTablesOptions;
|
|
297
250
|
|
|
298
|
-
//
|
|
299
|
-
|
|
251
|
+
// Socket handler function (when type is 'socket-handler')
|
|
252
|
+
socketHandler?: TSocketHandler;
|
|
300
253
|
}
|
|
301
254
|
|
|
302
255
|
/**
|
|
@@ -175,13 +175,12 @@ export class NFTablesManager {
|
|
|
175
175
|
};
|
|
176
176
|
|
|
177
177
|
// Add security-related options
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
options.ipAllowList = security.ipAllowList;
|
|
178
|
+
if (route.security?.ipAllowList?.length) {
|
|
179
|
+
options.ipAllowList = route.security.ipAllowList;
|
|
181
180
|
}
|
|
182
181
|
|
|
183
|
-
if (security?.ipBlockList?.length) {
|
|
184
|
-
options.ipBlockList = security.ipBlockList;
|
|
182
|
+
if (route.security?.ipBlockList?.length) {
|
|
183
|
+
options.ipBlockList = route.security.ipBlockList;
|
|
185
184
|
}
|
|
186
185
|
|
|
187
186
|
// Add QoS options
|
|
@@ -10,7 +10,6 @@ import { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
|
10
10
|
import { TimeoutManager } from './timeout-manager.js';
|
|
11
11
|
import { RouteManager } from './route-manager.js';
|
|
12
12
|
import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
|
|
13
|
-
import { RedirectHandler, StaticHandler } from '../http-proxy/handlers/index.js';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Handles new connection processing and setup logic with support for route-based configuration
|
|
@@ -147,18 +146,42 @@ export class RouteConnectionHandler {
|
|
|
147
146
|
);
|
|
148
147
|
}
|
|
149
148
|
|
|
150
|
-
//
|
|
151
|
-
this.
|
|
149
|
+
// Handle the connection - wait for initial data to determine if it's TLS
|
|
150
|
+
this.handleInitialData(socket, record);
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
/**
|
|
155
|
-
* Handle
|
|
154
|
+
* Handle initial data from a connection to determine routing
|
|
156
155
|
*/
|
|
157
|
-
private
|
|
156
|
+
private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
|
|
158
157
|
const connectionId = record.id;
|
|
159
158
|
const localPort = record.localPort;
|
|
160
159
|
let initialDataReceived = false;
|
|
161
160
|
|
|
161
|
+
// Check if any routes on this port require TLS handling
|
|
162
|
+
const allRoutes = this.routeManager.getAllRoutes();
|
|
163
|
+
const needsTlsHandling = allRoutes.some(route => {
|
|
164
|
+
// Check if route matches this port
|
|
165
|
+
const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
|
|
166
|
+
|
|
167
|
+
return matchesPort &&
|
|
168
|
+
route.action.type === 'forward' &&
|
|
169
|
+
route.action.tls &&
|
|
170
|
+
(route.action.tls.mode === 'terminate' ||
|
|
171
|
+
route.action.tls.mode === 'passthrough');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// If no routes require TLS handling and it's not port 443, route immediately
|
|
175
|
+
if (!needsTlsHandling && localPort !== 443) {
|
|
176
|
+
// Set up error handler
|
|
177
|
+
socket.on('error', this.connectionManager.handleError('incoming', record));
|
|
178
|
+
|
|
179
|
+
// Route immediately for non-TLS connections
|
|
180
|
+
this.routeConnection(socket, record, '', undefined);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Otherwise, wait for initial data to check if it's TLS
|
|
162
185
|
// Set an initial timeout for handshake data
|
|
163
186
|
let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
|
|
164
187
|
if (!initialDataReceived) {
|
|
@@ -297,6 +320,12 @@ export class RouteConnectionHandler {
|
|
|
297
320
|
const localPort = record.localPort;
|
|
298
321
|
const remoteIP = record.remoteIP;
|
|
299
322
|
|
|
323
|
+
// Check if this is an HTTP proxy port
|
|
324
|
+
const isHttpProxyPort = this.settings.useHttpProxy?.includes(localPort);
|
|
325
|
+
|
|
326
|
+
// For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
|
|
327
|
+
const skipDomainCheck = isHttpProxyPort && !record.isTLS;
|
|
328
|
+
|
|
300
329
|
// Find matching route
|
|
301
330
|
const routeMatch = this.routeManager.findMatchingRoute({
|
|
302
331
|
port: localPort,
|
|
@@ -304,6 +333,7 @@ export class RouteConnectionHandler {
|
|
|
304
333
|
clientIp: remoteIP,
|
|
305
334
|
path: undefined, // We don't have path info at this point
|
|
306
335
|
tlsVersion: undefined, // We don't extract TLS version yet
|
|
336
|
+
skipDomainCheck: skipDomainCheck,
|
|
307
337
|
});
|
|
308
338
|
|
|
309
339
|
if (!routeMatch) {
|
|
@@ -383,20 +413,69 @@ export class RouteConnectionHandler {
|
|
|
383
413
|
});
|
|
384
414
|
}
|
|
385
415
|
|
|
416
|
+
// Apply route-specific security checks
|
|
417
|
+
if (route.security) {
|
|
418
|
+
// Check IP allow/block lists
|
|
419
|
+
if (route.security.ipAllowList || route.security.ipBlockList) {
|
|
420
|
+
const isIPAllowed = this.securityManager.isIPAuthorized(
|
|
421
|
+
remoteIP,
|
|
422
|
+
route.security.ipAllowList || [],
|
|
423
|
+
route.security.ipBlockList || []
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
if (!isIPAllowed) {
|
|
427
|
+
logger.log('warn', `IP ${remoteIP} blocked by route security for route ${route.name || 'unnamed'} (connection: ${connectionId})`, {
|
|
428
|
+
connectionId,
|
|
429
|
+
remoteIP,
|
|
430
|
+
routeName: route.name || 'unnamed',
|
|
431
|
+
component: 'route-handler'
|
|
432
|
+
});
|
|
433
|
+
socket.end();
|
|
434
|
+
this.connectionManager.cleanupConnection(record, 'route_ip_blocked');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Check max connections per route
|
|
440
|
+
if (route.security.maxConnections !== undefined) {
|
|
441
|
+
// TODO: Implement per-route connection tracking
|
|
442
|
+
// For now, log that this feature is not yet implemented
|
|
443
|
+
if (this.settings.enableDetailedLogging) {
|
|
444
|
+
logger.log('warn', `Route ${route.name} has maxConnections=${route.security.maxConnections} configured but per-route connection limits are not yet implemented`, {
|
|
445
|
+
connectionId,
|
|
446
|
+
routeName: route.name,
|
|
447
|
+
component: 'route-handler'
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Check authentication requirements
|
|
453
|
+
if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
|
|
454
|
+
// Authentication checks would typically happen at the HTTP layer
|
|
455
|
+
// For non-HTTP connections or passthrough, we can't enforce authentication
|
|
456
|
+
if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
|
|
457
|
+
logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
|
|
458
|
+
connectionId,
|
|
459
|
+
routeName: route.name,
|
|
460
|
+
tlsMode: route.action.tls?.mode || 'none',
|
|
461
|
+
component: 'route-handler'
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
386
466
|
|
|
387
467
|
// Handle the route based on its action type
|
|
388
468
|
switch (route.action.type) {
|
|
389
469
|
case 'forward':
|
|
390
470
|
return this.handleForwardAction(socket, record, route, initialChunk);
|
|
391
471
|
|
|
392
|
-
case '
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
this.handleStaticAction(socket, record, route, initialChunk);
|
|
472
|
+
case 'socket-handler':
|
|
473
|
+
logger.log('info', `Handling socket-handler action for route ${route.name}`, {
|
|
474
|
+
connectionId,
|
|
475
|
+
routeName: route.name,
|
|
476
|
+
component: 'route-handler'
|
|
477
|
+
});
|
|
478
|
+
this.handleSocketHandlerAction(socket, record, route, initialChunk);
|
|
400
479
|
return;
|
|
401
480
|
|
|
402
481
|
default:
|
|
@@ -636,6 +715,18 @@ export class RouteConnectionHandler {
|
|
|
636
715
|
// No TLS settings - check if this port should use HttpProxy
|
|
637
716
|
const isHttpProxyPort = this.settings.useHttpProxy?.includes(record.localPort);
|
|
638
717
|
|
|
718
|
+
// Debug logging
|
|
719
|
+
if (this.settings.enableDetailedLogging) {
|
|
720
|
+
logger.log('debug', `Checking HttpProxy forwarding: port=${record.localPort}, useHttpProxy=${JSON.stringify(this.settings.useHttpProxy)}, isHttpProxyPort=${isHttpProxyPort}, hasHttpProxy=${!!this.httpProxyBridge.getHttpProxy()}`, {
|
|
721
|
+
connectionId,
|
|
722
|
+
localPort: record.localPort,
|
|
723
|
+
useHttpProxy: this.settings.useHttpProxy,
|
|
724
|
+
isHttpProxyPort,
|
|
725
|
+
hasHttpProxy: !!this.httpProxyBridge.getHttpProxy(),
|
|
726
|
+
component: 'route-handler'
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
639
730
|
if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
|
|
640
731
|
// Forward non-TLS connections to HttpProxy if configured
|
|
641
732
|
if (this.settings.enableDetailedLogging) {
|
|
@@ -710,70 +801,85 @@ export class RouteConnectionHandler {
|
|
|
710
801
|
}
|
|
711
802
|
|
|
712
803
|
/**
|
|
713
|
-
* Handle a
|
|
804
|
+
* Handle a socket-handler action for a route
|
|
714
805
|
*/
|
|
715
|
-
private
|
|
806
|
+
private async handleSocketHandlerAction(
|
|
716
807
|
socket: plugins.net.Socket,
|
|
717
808
|
record: IConnectionRecord,
|
|
718
|
-
route: IRouteConfig
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
809
|
+
route: IRouteConfig,
|
|
810
|
+
initialChunk?: Buffer
|
|
811
|
+
): Promise<void> {
|
|
812
|
+
const connectionId = record.id;
|
|
813
|
+
|
|
814
|
+
if (!route.action.socketHandler) {
|
|
815
|
+
logger.log('error', 'socket-handler action missing socketHandler function', {
|
|
816
|
+
connectionId,
|
|
817
|
+
routeName: route.name,
|
|
724
818
|
component: 'route-handler'
|
|
725
819
|
});
|
|
726
|
-
socket.
|
|
727
|
-
this.connectionManager.cleanupConnection(record, '
|
|
820
|
+
socket.destroy();
|
|
821
|
+
this.connectionManager.cleanupConnection(record, 'missing_handler');
|
|
728
822
|
return;
|
|
729
823
|
}
|
|
730
|
-
|
|
731
|
-
//
|
|
732
|
-
|
|
824
|
+
|
|
825
|
+
// Create route context for the handler
|
|
826
|
+
const routeContext = this.createRouteContext({
|
|
733
827
|
connectionId: record.id,
|
|
734
|
-
|
|
735
|
-
|
|
828
|
+
port: record.localPort,
|
|
829
|
+
domain: record.lockedDomain,
|
|
830
|
+
clientIp: record.remoteIP,
|
|
831
|
+
serverIp: socket.localAddress || '',
|
|
832
|
+
isTls: record.isTLS || false,
|
|
833
|
+
tlsVersion: record.tlsVersion,
|
|
834
|
+
routeName: route.name,
|
|
835
|
+
routeId: route.id,
|
|
736
836
|
});
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
837
|
+
|
|
838
|
+
try {
|
|
839
|
+
// Call the handler with socket AND context
|
|
840
|
+
const result = route.action.socketHandler(socket, routeContext);
|
|
841
|
+
|
|
842
|
+
// Handle async handlers properly
|
|
843
|
+
if (result instanceof Promise) {
|
|
844
|
+
result
|
|
845
|
+
.then(() => {
|
|
846
|
+
// Emit initial chunk after async handler completes
|
|
847
|
+
if (initialChunk && initialChunk.length > 0) {
|
|
848
|
+
socket.emit('data', initialChunk);
|
|
849
|
+
}
|
|
850
|
+
})
|
|
851
|
+
.catch(error => {
|
|
852
|
+
logger.log('error', 'Socket handler error', {
|
|
853
|
+
connectionId,
|
|
854
|
+
routeName: route.name,
|
|
855
|
+
error: error.message,
|
|
856
|
+
component: 'route-handler'
|
|
857
|
+
});
|
|
858
|
+
if (!socket.destroyed) {
|
|
859
|
+
socket.destroy();
|
|
860
|
+
}
|
|
861
|
+
this.connectionManager.cleanupConnection(record, 'handler_error');
|
|
862
|
+
});
|
|
863
|
+
} else {
|
|
864
|
+
// For sync handlers, emit on next tick
|
|
865
|
+
if (initialChunk && initialChunk.length > 0) {
|
|
866
|
+
process.nextTick(() => {
|
|
867
|
+
socket.emit('data', initialChunk);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
} catch (error) {
|
|
872
|
+
logger.log('error', 'Socket handler error', {
|
|
751
873
|
connectionId,
|
|
752
|
-
routeName: route.name
|
|
874
|
+
routeName: route.name,
|
|
875
|
+
error: error.message,
|
|
753
876
|
component: 'route-handler'
|
|
754
877
|
});
|
|
878
|
+
if (!socket.destroyed) {
|
|
879
|
+
socket.destroy();
|
|
880
|
+
}
|
|
881
|
+
this.connectionManager.cleanupConnection(record, 'handler_error');
|
|
755
882
|
}
|
|
756
|
-
|
|
757
|
-
// Simply close the connection
|
|
758
|
-
socket.end();
|
|
759
|
-
this.connectionManager.initiateCleanupOnce(record, 'route_blocked');
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
/**
|
|
763
|
-
* Handle a static action for a route
|
|
764
|
-
*/
|
|
765
|
-
private async handleStaticAction(
|
|
766
|
-
socket: plugins.net.Socket,
|
|
767
|
-
record: IConnectionRecord,
|
|
768
|
-
route: IRouteConfig,
|
|
769
|
-
initialChunk?: Buffer
|
|
770
|
-
): Promise<void> {
|
|
771
|
-
// Delegate to HttpProxy's StaticHandler
|
|
772
|
-
await StaticHandler.handleStatic(socket, route, {
|
|
773
|
-
connectionId: record.id,
|
|
774
|
-
connectionManager: this.connectionManager,
|
|
775
|
-
settings: this.settings
|
|
776
|
-
}, record, initialChunk);
|
|
777
883
|
}
|
|
778
884
|
|
|
779
885
|
/**
|