@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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route matching utilities for SmartProxy components
|
|
3
|
+
*
|
|
4
|
+
* This file provides utility functions that use the unified matchers
|
|
5
|
+
* and additional route-specific utilities.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { DomainMatcher, PathMatcher, IpMatcher, HeaderMatcher } from './matchers/index.js';
|
|
9
|
+
import { RouteSpecificity } from './specificity.js';
|
|
10
|
+
import type { IRouteSpecificity } from './types.js';
|
|
11
|
+
import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Match domains from a route against a given domain
|
|
16
|
+
*
|
|
17
|
+
* @param domains Array or single domain pattern to match against
|
|
18
|
+
* @param domain Domain to match
|
|
19
|
+
* @returns Whether the domain matches any of the patterns
|
|
20
|
+
*/
|
|
21
|
+
export function matchRouteDomain(domains: string | string[] | undefined, domain: string | undefined): boolean {
|
|
22
|
+
// If no domains specified in the route, match all domains
|
|
23
|
+
if (!domains) {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// If no domain in the request, can't match domain-specific routes
|
|
28
|
+
if (!domain) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const patterns = Array.isArray(domains) ? domains : [domains];
|
|
33
|
+
return patterns.some(pattern => DomainMatcher.match(pattern, domain));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculate route specificity score
|
|
40
|
+
* Higher score means more specific matching criteria
|
|
41
|
+
*
|
|
42
|
+
* @param match Match criteria to evaluate
|
|
43
|
+
* @returns Numeric specificity score
|
|
44
|
+
*/
|
|
45
|
+
export function calculateRouteSpecificity(match: {
|
|
46
|
+
domains?: string | string[];
|
|
47
|
+
path?: string;
|
|
48
|
+
clientIp?: string[];
|
|
49
|
+
tlsVersion?: string[];
|
|
50
|
+
headers?: Record<string, string | RegExp>;
|
|
51
|
+
}): number {
|
|
52
|
+
let score = 0;
|
|
53
|
+
|
|
54
|
+
// Path specificity using PathMatcher
|
|
55
|
+
if (match.path) {
|
|
56
|
+
score += PathMatcher.calculateSpecificity(match.path);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Domain specificity using DomainMatcher
|
|
60
|
+
if (match.domains) {
|
|
61
|
+
const domains = Array.isArray(match.domains) ? match.domains : [match.domains];
|
|
62
|
+
// Use the highest specificity among all domains
|
|
63
|
+
const domainScore = Math.max(...domains.map(d => DomainMatcher.calculateSpecificity(d)));
|
|
64
|
+
score += domainScore;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Headers specificity using HeaderMatcher
|
|
68
|
+
if (match.headers) {
|
|
69
|
+
const stringHeaders: Record<string, string> = {};
|
|
70
|
+
for (const [key, value] of Object.entries(match.headers)) {
|
|
71
|
+
stringHeaders[key] = value instanceof RegExp ? value.source : value;
|
|
72
|
+
}
|
|
73
|
+
score += HeaderMatcher.calculateSpecificity(stringHeaders);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Client IP adds some specificity
|
|
77
|
+
if (match.clientIp && match.clientIp.length > 0) {
|
|
78
|
+
// Use the first IP pattern for specificity
|
|
79
|
+
score += IpMatcher.calculateSpecificity(match.clientIp[0]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// TLS version adds minimal specificity
|
|
83
|
+
if (match.tlsVersion && match.tlsVersion.length > 0) {
|
|
84
|
+
score += match.tlsVersion.length * 10;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return score;
|
|
88
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
|
|
2
|
+
import type { IRouteSpecificity } from './types.js';
|
|
3
|
+
import { DomainMatcher, PathMatcher, IpMatcher, HeaderMatcher } from './matchers/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unified route specificity calculator
|
|
7
|
+
* Provides consistent specificity scoring across all routing components
|
|
8
|
+
*/
|
|
9
|
+
export class RouteSpecificity {
|
|
10
|
+
/**
|
|
11
|
+
* Calculate the total specificity score for a route
|
|
12
|
+
* Higher scores indicate more specific routes that should match first
|
|
13
|
+
*/
|
|
14
|
+
static calculate(route: IRouteConfig): IRouteSpecificity {
|
|
15
|
+
const specificity: IRouteSpecificity = {
|
|
16
|
+
pathSpecificity: 0,
|
|
17
|
+
domainSpecificity: 0,
|
|
18
|
+
ipSpecificity: 0,
|
|
19
|
+
headerSpecificity: 0,
|
|
20
|
+
tlsSpecificity: 0,
|
|
21
|
+
totalScore: 0
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Path specificity
|
|
25
|
+
if (route.match.path) {
|
|
26
|
+
specificity.pathSpecificity = PathMatcher.calculateSpecificity(route.match.path);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Domain specificity
|
|
30
|
+
if (route.match.domains) {
|
|
31
|
+
const domains = Array.isArray(route.match.domains)
|
|
32
|
+
? route.match.domains
|
|
33
|
+
: [route.match.domains];
|
|
34
|
+
|
|
35
|
+
// Use the highest specificity among all domains
|
|
36
|
+
specificity.domainSpecificity = Math.max(
|
|
37
|
+
...domains.map(d => DomainMatcher.calculateSpecificity(d))
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// IP specificity (clientIp is an array of IPs)
|
|
42
|
+
if (route.match.clientIp && route.match.clientIp.length > 0) {
|
|
43
|
+
// Use the first IP pattern for specificity calculation
|
|
44
|
+
specificity.ipSpecificity = IpMatcher.calculateSpecificity(route.match.clientIp[0]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Header specificity (convert RegExp values to strings)
|
|
48
|
+
if (route.match.headers) {
|
|
49
|
+
const stringHeaders: Record<string, string> = {};
|
|
50
|
+
for (const [key, value] of Object.entries(route.match.headers)) {
|
|
51
|
+
stringHeaders[key] = value instanceof RegExp ? value.source : value;
|
|
52
|
+
}
|
|
53
|
+
specificity.headerSpecificity = HeaderMatcher.calculateSpecificity(stringHeaders);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// TLS version specificity
|
|
57
|
+
if (route.match.tlsVersion && route.match.tlsVersion.length > 0) {
|
|
58
|
+
specificity.tlsSpecificity = route.match.tlsVersion.length * 10;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Calculate total score with weights
|
|
62
|
+
specificity.totalScore =
|
|
63
|
+
specificity.pathSpecificity * 3 + // Path is most important
|
|
64
|
+
specificity.domainSpecificity * 2 + // Domain is second
|
|
65
|
+
specificity.ipSpecificity * 1.5 + // IP is moderately important
|
|
66
|
+
specificity.headerSpecificity * 1 + // Headers are less important
|
|
67
|
+
specificity.tlsSpecificity * 0.5; // TLS is least important
|
|
68
|
+
|
|
69
|
+
return specificity;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compare two routes and determine which is more specific
|
|
74
|
+
* @returns positive if route1 is more specific, negative if route2 is more specific, 0 if equal
|
|
75
|
+
*/
|
|
76
|
+
static compare(route1: IRouteConfig, route2: IRouteConfig): number {
|
|
77
|
+
const spec1 = this.calculate(route1);
|
|
78
|
+
const spec2 = this.calculate(route2);
|
|
79
|
+
|
|
80
|
+
// First compare by total score
|
|
81
|
+
if (spec1.totalScore !== spec2.totalScore) {
|
|
82
|
+
return spec1.totalScore - spec2.totalScore;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If total scores are equal, compare by individual components
|
|
86
|
+
// Path is most important tiebreaker
|
|
87
|
+
if (spec1.pathSpecificity !== spec2.pathSpecificity) {
|
|
88
|
+
return spec1.pathSpecificity - spec2.pathSpecificity;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Then domain
|
|
92
|
+
if (spec1.domainSpecificity !== spec2.domainSpecificity) {
|
|
93
|
+
return spec1.domainSpecificity - spec2.domainSpecificity;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Then IP
|
|
97
|
+
if (spec1.ipSpecificity !== spec2.ipSpecificity) {
|
|
98
|
+
return spec1.ipSpecificity - spec2.ipSpecificity;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Then headers
|
|
102
|
+
if (spec1.headerSpecificity !== spec2.headerSpecificity) {
|
|
103
|
+
return spec1.headerSpecificity - spec2.headerSpecificity;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Finally TLS
|
|
107
|
+
return spec1.tlsSpecificity - spec2.tlsSpecificity;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sort routes by specificity (most specific first)
|
|
112
|
+
*/
|
|
113
|
+
static sort(routes: IRouteConfig[]): IRouteConfig[] {
|
|
114
|
+
return [...routes].sort((a, b) => this.compare(b, a));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Find the most specific route from a list
|
|
119
|
+
*/
|
|
120
|
+
static findMostSpecific(routes: IRouteConfig[]): IRouteConfig | null {
|
|
121
|
+
if (routes.length === 0) return null;
|
|
122
|
+
|
|
123
|
+
return routes.reduce((most, current) =>
|
|
124
|
+
this.compare(current, most) > 0 ? current : most
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if a route has any matching criteria
|
|
130
|
+
*/
|
|
131
|
+
static hasMatchCriteria(route: IRouteConfig): boolean {
|
|
132
|
+
const match = route.match;
|
|
133
|
+
return !!(
|
|
134
|
+
match.domains ||
|
|
135
|
+
match.path ||
|
|
136
|
+
match.clientIp?.length ||
|
|
137
|
+
match.headers ||
|
|
138
|
+
match.tlsVersion?.length
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core routing types used throughout the routing system
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface IPathMatchResult {
|
|
6
|
+
matches: boolean;
|
|
7
|
+
params?: Record<string, string>;
|
|
8
|
+
pathMatch?: string;
|
|
9
|
+
pathRemainder?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface IRouteMatchResult {
|
|
13
|
+
matches: boolean;
|
|
14
|
+
score: number;
|
|
15
|
+
specificity: number;
|
|
16
|
+
matchedCriteria: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IDomainMatchOptions {
|
|
20
|
+
allowWildcards?: boolean;
|
|
21
|
+
caseInsensitive?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IIpMatchOptions {
|
|
25
|
+
allowCidr?: boolean;
|
|
26
|
+
allowRanges?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IHeaderMatchOptions {
|
|
30
|
+
caseInsensitive?: boolean;
|
|
31
|
+
exactMatch?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface IRouteSpecificity {
|
|
35
|
+
pathSpecificity: number;
|
|
36
|
+
domainSpecificity: number;
|
|
37
|
+
ipSpecificity: number;
|
|
38
|
+
headerSpecificity: number;
|
|
39
|
+
tlsSpecificity: number;
|
|
40
|
+
totalScore: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IMatcher<T = any, O = any> {
|
|
44
|
+
match(pattern: string, value: string, options?: O): T | boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface IAsyncMatcher<T = any, O = any> {
|
|
48
|
+
match(pattern: string, value: string, options?: O): Promise<T | boolean>;
|
|
49
|
+
}
|
package/ts/core/utils/index.ts
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
export * from './validation-utils.js';
|
|
6
6
|
export * from './ip-utils.js';
|
|
7
7
|
export * from './template-utils.js';
|
|
8
|
-
export * from './route-manager.js';
|
|
9
|
-
export * from './route-utils.js';
|
|
10
8
|
export * from './security-utils.js';
|
|
11
9
|
export * from './shared-security-manager.js';
|
|
12
10
|
export * from './websocket-utils.js';
|
|
@@ -17,3 +15,4 @@ export * from './lifecycle-component.js';
|
|
|
17
15
|
export * from './binary-heap.js';
|
|
18
16
|
export * from './enhanced-connection-pool.js';
|
|
19
17
|
export * from './socket-utils.js';
|
|
18
|
+
export * from './proxy-protocol.js';
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { logger } from './logger.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface representing parsed PROXY protocol information
|
|
6
|
+
*/
|
|
7
|
+
export interface IProxyInfo {
|
|
8
|
+
protocol: 'TCP4' | 'TCP6' | 'UNKNOWN';
|
|
9
|
+
sourceIP: string;
|
|
10
|
+
sourcePort: number;
|
|
11
|
+
destinationIP: string;
|
|
12
|
+
destinationPort: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Interface for parse result including remaining data
|
|
17
|
+
*/
|
|
18
|
+
export interface IProxyParseResult {
|
|
19
|
+
proxyInfo: IProxyInfo | null;
|
|
20
|
+
remainingData: Buffer;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Parser for PROXY protocol v1 (text format)
|
|
25
|
+
* Spec: https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
|
|
26
|
+
*/
|
|
27
|
+
export class ProxyProtocolParser {
|
|
28
|
+
static readonly PROXY_V1_SIGNATURE = 'PROXY ';
|
|
29
|
+
static readonly MAX_HEADER_LENGTH = 107; // Max length for v1 header
|
|
30
|
+
static readonly HEADER_TERMINATOR = '\r\n';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse PROXY protocol v1 header from buffer
|
|
34
|
+
* Returns proxy info and remaining data after header
|
|
35
|
+
*/
|
|
36
|
+
static parse(data: Buffer): IProxyParseResult {
|
|
37
|
+
// Check if buffer starts with PROXY signature
|
|
38
|
+
if (!data.toString('ascii', 0, 6).startsWith(this.PROXY_V1_SIGNATURE)) {
|
|
39
|
+
return {
|
|
40
|
+
proxyInfo: null,
|
|
41
|
+
remainingData: data
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Find header terminator
|
|
46
|
+
const headerEndIndex = data.indexOf(this.HEADER_TERMINATOR);
|
|
47
|
+
if (headerEndIndex === -1) {
|
|
48
|
+
// Header incomplete, need more data
|
|
49
|
+
if (data.length > this.MAX_HEADER_LENGTH) {
|
|
50
|
+
// Header too long, invalid
|
|
51
|
+
throw new Error('PROXY protocol header exceeds maximum length');
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
proxyInfo: null,
|
|
55
|
+
remainingData: data
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extract header line
|
|
60
|
+
const headerLine = data.toString('ascii', 0, headerEndIndex);
|
|
61
|
+
const remainingData = data.slice(headerEndIndex + 2); // Skip \r\n
|
|
62
|
+
|
|
63
|
+
// Parse header
|
|
64
|
+
const parts = headerLine.split(' ');
|
|
65
|
+
|
|
66
|
+
if (parts.length < 2) {
|
|
67
|
+
throw new Error(`Invalid PROXY protocol header format: ${headerLine}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const [signature, protocol] = parts;
|
|
71
|
+
|
|
72
|
+
// Validate protocol
|
|
73
|
+
if (!['TCP4', 'TCP6', 'UNKNOWN'].includes(protocol)) {
|
|
74
|
+
throw new Error(`Invalid PROXY protocol: ${protocol}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// For UNKNOWN protocol, ignore addresses
|
|
78
|
+
if (protocol === 'UNKNOWN') {
|
|
79
|
+
return {
|
|
80
|
+
proxyInfo: {
|
|
81
|
+
protocol: 'UNKNOWN',
|
|
82
|
+
sourceIP: '',
|
|
83
|
+
sourcePort: 0,
|
|
84
|
+
destinationIP: '',
|
|
85
|
+
destinationPort: 0
|
|
86
|
+
},
|
|
87
|
+
remainingData
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// For TCP4/TCP6, we need all 6 parts
|
|
92
|
+
if (parts.length !== 6) {
|
|
93
|
+
throw new Error(`Invalid PROXY protocol header format: ${headerLine}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const [, , srcIP, dstIP, srcPort, dstPort] = parts;
|
|
97
|
+
|
|
98
|
+
// Validate and parse ports
|
|
99
|
+
const sourcePort = parseInt(srcPort, 10);
|
|
100
|
+
const destinationPort = parseInt(dstPort, 10);
|
|
101
|
+
|
|
102
|
+
if (isNaN(sourcePort) || sourcePort < 0 || sourcePort > 65535) {
|
|
103
|
+
throw new Error(`Invalid source port: ${srcPort}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (isNaN(destinationPort) || destinationPort < 0 || destinationPort > 65535) {
|
|
107
|
+
throw new Error(`Invalid destination port: ${dstPort}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Validate IP addresses
|
|
111
|
+
const protocolType = protocol as 'TCP4' | 'TCP6' | 'UNKNOWN';
|
|
112
|
+
if (!this.isValidIP(srcIP, protocolType)) {
|
|
113
|
+
throw new Error(`Invalid source IP for ${protocol}: ${srcIP}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (!this.isValidIP(dstIP, protocolType)) {
|
|
117
|
+
throw new Error(`Invalid destination IP for ${protocol}: ${dstIP}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
proxyInfo: {
|
|
122
|
+
protocol: protocol as 'TCP4' | 'TCP6',
|
|
123
|
+
sourceIP: srcIP,
|
|
124
|
+
sourcePort,
|
|
125
|
+
destinationIP: dstIP,
|
|
126
|
+
destinationPort
|
|
127
|
+
},
|
|
128
|
+
remainingData
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate PROXY protocol v1 header
|
|
134
|
+
*/
|
|
135
|
+
static generate(info: IProxyInfo): Buffer {
|
|
136
|
+
if (info.protocol === 'UNKNOWN') {
|
|
137
|
+
return Buffer.from(`PROXY UNKNOWN\r\n`, 'ascii');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const header = `PROXY ${info.protocol} ${info.sourceIP} ${info.destinationIP} ${info.sourcePort} ${info.destinationPort}\r\n`;
|
|
141
|
+
|
|
142
|
+
if (header.length > this.MAX_HEADER_LENGTH) {
|
|
143
|
+
throw new Error('Generated PROXY protocol header exceeds maximum length');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return Buffer.from(header, 'ascii');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Validate IP address format
|
|
151
|
+
*/
|
|
152
|
+
private static isValidIP(ip: string, protocol: 'TCP4' | 'TCP6' | 'UNKNOWN'): boolean {
|
|
153
|
+
if (protocol === 'TCP4') {
|
|
154
|
+
return plugins.net.isIPv4(ip);
|
|
155
|
+
} else if (protocol === 'TCP6') {
|
|
156
|
+
return plugins.net.isIPv6(ip);
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Attempt to read a complete PROXY protocol header from a socket
|
|
163
|
+
* Returns null if no PROXY protocol detected or incomplete
|
|
164
|
+
*/
|
|
165
|
+
static async readFromSocket(socket: plugins.net.Socket, timeout: number = 5000): Promise<IProxyParseResult | null> {
|
|
166
|
+
return new Promise((resolve) => {
|
|
167
|
+
let buffer = Buffer.alloc(0);
|
|
168
|
+
let resolved = false;
|
|
169
|
+
|
|
170
|
+
const cleanup = () => {
|
|
171
|
+
socket.removeListener('data', onData);
|
|
172
|
+
socket.removeListener('error', onError);
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const timer = setTimeout(() => {
|
|
177
|
+
if (!resolved) {
|
|
178
|
+
resolved = true;
|
|
179
|
+
cleanup();
|
|
180
|
+
resolve({
|
|
181
|
+
proxyInfo: null,
|
|
182
|
+
remainingData: buffer
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}, timeout);
|
|
186
|
+
|
|
187
|
+
const onData = (chunk: Buffer) => {
|
|
188
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
189
|
+
|
|
190
|
+
// Check if we have enough data
|
|
191
|
+
if (!buffer.toString('ascii', 0, Math.min(6, buffer.length)).startsWith(this.PROXY_V1_SIGNATURE)) {
|
|
192
|
+
// Not PROXY protocol
|
|
193
|
+
resolved = true;
|
|
194
|
+
cleanup();
|
|
195
|
+
resolve({
|
|
196
|
+
proxyInfo: null,
|
|
197
|
+
remainingData: buffer
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Try to parse
|
|
203
|
+
try {
|
|
204
|
+
const result = this.parse(buffer);
|
|
205
|
+
if (result.proxyInfo) {
|
|
206
|
+
// Successfully parsed
|
|
207
|
+
resolved = true;
|
|
208
|
+
cleanup();
|
|
209
|
+
resolve(result);
|
|
210
|
+
} else if (buffer.length > this.MAX_HEADER_LENGTH) {
|
|
211
|
+
// Header too long
|
|
212
|
+
resolved = true;
|
|
213
|
+
cleanup();
|
|
214
|
+
resolve({
|
|
215
|
+
proxyInfo: null,
|
|
216
|
+
remainingData: buffer
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// Otherwise continue reading
|
|
220
|
+
} catch (error) {
|
|
221
|
+
// Parse error
|
|
222
|
+
logger.log('error', `PROXY protocol parse error: ${error.message}`);
|
|
223
|
+
resolved = true;
|
|
224
|
+
cleanup();
|
|
225
|
+
resolve({
|
|
226
|
+
proxyInfo: null,
|
|
227
|
+
remainingData: buffer
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const onError = (error: Error) => {
|
|
233
|
+
logger.log('error', `Socket error while reading PROXY protocol: ${error.message}`);
|
|
234
|
+
resolved = true;
|
|
235
|
+
cleanup();
|
|
236
|
+
resolve({
|
|
237
|
+
proxyInfo: null,
|
|
238
|
+
remainingData: buffer
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
socket.on('data', onData);
|
|
243
|
+
socket.on('error', onError);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
-
import {
|
|
3
|
-
matchIpPattern,
|
|
4
|
-
ipToNumber,
|
|
5
|
-
matchIpCidr
|
|
6
|
-
} from './route-utils.js';
|
|
2
|
+
import { IpMatcher } from '../routing/matchers/ip.js';
|
|
7
3
|
|
|
8
4
|
/**
|
|
9
5
|
* Security utilities for IP validation, rate limiting,
|
|
@@ -90,7 +86,7 @@ export function isIPAuthorized(
|
|
|
90
86
|
// First check if IP is blocked - blocked IPs take precedence
|
|
91
87
|
if (blockedIPs.length > 0) {
|
|
92
88
|
for (const pattern of blockedIPs) {
|
|
93
|
-
if (
|
|
89
|
+
if (IpMatcher.match(pattern, ip)) {
|
|
94
90
|
return false;
|
|
95
91
|
}
|
|
96
92
|
}
|
|
@@ -104,7 +100,7 @@ export function isIPAuthorized(
|
|
|
104
100
|
// Then check if IP is allowed in the explicit allow list
|
|
105
101
|
if (allowedIPs.length > 0) {
|
|
106
102
|
for (const pattern of allowedIPs) {
|
|
107
|
-
if (
|
|
103
|
+
if (IpMatcher.match(pattern, ip)) {
|
|
108
104
|
return true;
|
|
109
105
|
}
|
|
110
106
|
}
|
package/ts/index.ts
CHANGED
|
@@ -2,28 +2,18 @@
|
|
|
2
2
|
* SmartProxy main module exports
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
// Migrated to the new proxies structure
|
|
5
|
+
// NFTables proxy exports
|
|
7
6
|
export * from './proxies/nftables-proxy/index.js';
|
|
8
7
|
|
|
9
|
-
// Export HttpProxy elements
|
|
8
|
+
// Export HttpProxy elements
|
|
10
9
|
export { HttpProxy, CertificateManager, ConnectionPool, RequestHandler, WebSocketHandler } from './proxies/http-proxy/index.js';
|
|
11
10
|
export type { IMetricsTracker, MetricsTracker } from './proxies/http-proxy/index.js';
|
|
12
|
-
// Export models except IAcmeOptions to avoid conflict
|
|
13
11
|
export type { IHttpProxyOptions, ICertificateEntry, ILogger } from './proxies/http-proxy/models/types.js';
|
|
14
|
-
export {
|
|
15
|
-
|
|
16
|
-
// Backward compatibility exports (deprecated)
|
|
17
|
-
export { HttpProxy as NetworkProxy } from './proxies/http-proxy/index.js';
|
|
18
|
-
export type { IHttpProxyOptions as INetworkProxyOptions } from './proxies/http-proxy/models/types.js';
|
|
19
|
-
export { HttpProxyBridge as NetworkProxyBridge } from './proxies/smart-proxy/index.js';
|
|
20
|
-
|
|
21
|
-
// Certificate and Port80 modules have been removed - use SmartCertManager instead
|
|
22
|
-
// Redirect module has been removed - use route-based redirects instead
|
|
12
|
+
export { SharedRouteManager as HttpProxyRouteManager } from './core/routing/route-manager.js';
|
|
23
13
|
|
|
24
14
|
// Export SmartProxy elements selectively to avoid RouteManager ambiguity
|
|
25
15
|
export { SmartProxy, ConnectionManager, SecurityManager, TimeoutManager, TlsManager, HttpProxyBridge, RouteConnectionHandler, SmartCertManager } from './proxies/smart-proxy/index.js';
|
|
26
|
-
export { RouteManager } from './
|
|
16
|
+
export { SharedRouteManager as RouteManager } from './core/routing/route-manager.js';
|
|
27
17
|
// Export smart-proxy models
|
|
28
18
|
export type { ISmartProxyOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext } from './proxies/smart-proxy/models/index.js';
|
|
29
19
|
export type { TSmartProxyCertProvisionObject } from './proxies/smart-proxy/models/interfaces.js';
|