@push.rocks/smartproxy 19.5.18 → 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/00_commitinfo_data.js +2 -2
- 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/core/utils/socket-utils.d.ts +0 -15
- package/dist_ts/core/utils/socket-utils.js +1 -35
- package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +47 -32
- package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +51 -32
- 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 +210 -1
- package/readme.plan.md +621 -0
- package/readme.routing.md +341 -0
- package/ts/00_commitinfo_data.ts +1 -1
- 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/core/utils/socket-utils.ts +0 -44
- package/ts/forwarding/handlers/https-terminate-to-http-handler.ts +47 -33
- package/ts/forwarding/handlers/https-terminate-to-https-handler.ts +55 -35
- 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
package/readme.problems.md
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# SmartProxy Performance Issues Report
|
|
2
|
-
|
|
3
|
-
## Executive Summary
|
|
4
|
-
This report identifies performance issues and blocking operations in the SmartProxy codebase that could impact scalability and responsiveness under high load.
|
|
5
|
-
|
|
6
|
-
## Critical Issues
|
|
7
|
-
|
|
8
|
-
### 1. **Synchronous Filesystem Operations**
|
|
9
|
-
These operations block the event loop and should be replaced with async alternatives:
|
|
10
|
-
|
|
11
|
-
#### Certificate Management
|
|
12
|
-
- `ts/proxies/http-proxy/certificate-manager.ts:29`: `fs.existsSync()`
|
|
13
|
-
- `ts/proxies/http-proxy/certificate-manager.ts:30`: `fs.mkdirSync()`
|
|
14
|
-
- `ts/proxies/http-proxy/certificate-manager.ts:49-50`: `fs.readFileSync()` for loading certificates
|
|
15
|
-
|
|
16
|
-
#### NFTables Proxy
|
|
17
|
-
- `ts/proxies/nftables-proxy/nftables-proxy.ts`: Multiple uses of `execSync()` for system commands
|
|
18
|
-
- `ts/proxies/nftables-proxy/nftables-proxy.ts`: Multiple `fs.writeFileSync()` and `fs.unlinkSync()` operations
|
|
19
|
-
|
|
20
|
-
#### Certificate Store
|
|
21
|
-
- `ts/proxies/smart-proxy/cert-store.ts:8`: `ensureDirSync()`
|
|
22
|
-
- `ts/proxies/smart-proxy/cert-store.ts:15,31,76`: `fileExistsSync()`
|
|
23
|
-
- `ts/proxies/smart-proxy/cert-store.ts:77`: `removeManySync()`
|
|
24
|
-
|
|
25
|
-
### 2. **Event Loop Blocking Operations**
|
|
26
|
-
|
|
27
|
-
#### Busy Wait Loop
|
|
28
|
-
- `ts/proxies/nftables-proxy/nftables-proxy.ts:235-238`:
|
|
29
|
-
```typescript
|
|
30
|
-
const waitUntil = Date.now() + retryDelayMs;
|
|
31
|
-
while (Date.now() < waitUntil) {
|
|
32
|
-
// busy wait - blocks event loop completely
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
This is extremely problematic as it blocks the entire Node.js event loop.
|
|
36
|
-
|
|
37
|
-
### 3. **Potential Memory Leaks**
|
|
38
|
-
|
|
39
|
-
#### Timer Management Issues
|
|
40
|
-
Several timers are created without proper cleanup:
|
|
41
|
-
- `ts/proxies/http-proxy/function-cache.ts`: `setInterval()` without storing reference for cleanup
|
|
42
|
-
- `ts/proxies/http-proxy/request-handler.ts`: `setInterval()` for rate limit cleanup without cleanup
|
|
43
|
-
- `ts/core/utils/shared-security-manager.ts`: `cleanupInterval` stored but no cleanup method
|
|
44
|
-
|
|
45
|
-
#### Event Listener Accumulation
|
|
46
|
-
- Multiple instances of event listeners being added without corresponding cleanup
|
|
47
|
-
- Connection handlers add listeners without always removing them on connection close
|
|
48
|
-
|
|
49
|
-
### 4. **Connection Pool Management**
|
|
50
|
-
|
|
51
|
-
#### ConnectionPool (ts/proxies/http-proxy/connection-pool.ts)
|
|
52
|
-
**Good practices observed:**
|
|
53
|
-
- Proper connection lifecycle management
|
|
54
|
-
- Periodic cleanup of idle connections
|
|
55
|
-
- Connection limits enforcement
|
|
56
|
-
|
|
57
|
-
**Potential issues:**
|
|
58
|
-
- No backpressure mechanism when pool is full
|
|
59
|
-
- Synchronous sorting operation in `cleanupConnectionPool()` could be slow with many connections
|
|
60
|
-
|
|
61
|
-
### 5. **Resource Management Issues**
|
|
62
|
-
|
|
63
|
-
#### Socket Cleanup
|
|
64
|
-
- Some error paths don't properly clean up sockets
|
|
65
|
-
- Missing `removeAllListeners()` in some error scenarios could lead to memory leaks
|
|
66
|
-
|
|
67
|
-
#### Timeout Management
|
|
68
|
-
- Inconsistent timeout handling across different components
|
|
69
|
-
- Some sockets created without timeout settings
|
|
70
|
-
|
|
71
|
-
### 6. **JSON Operations on Large Objects**
|
|
72
|
-
- `ts/proxies/smart-proxy/cert-store.ts:21`: `JSON.parse()` on certificate metadata
|
|
73
|
-
- `ts/proxies/smart-proxy/cert-store.ts:71`: `JSON.stringify()` with pretty printing
|
|
74
|
-
- `ts/proxies/http-proxy/function-cache.ts:76`: `JSON.stringify()` for cache keys (called frequently)
|
|
75
|
-
|
|
76
|
-
## Recommendations
|
|
77
|
-
|
|
78
|
-
### Immediate Actions (High Priority)
|
|
79
|
-
|
|
80
|
-
1. **Replace Synchronous Operations**
|
|
81
|
-
```typescript
|
|
82
|
-
// Instead of:
|
|
83
|
-
if (fs.existsSync(path)) { ... }
|
|
84
|
-
|
|
85
|
-
// Use:
|
|
86
|
-
try {
|
|
87
|
-
await fs.promises.access(path);
|
|
88
|
-
// file exists
|
|
89
|
-
} catch {
|
|
90
|
-
// file doesn't exist
|
|
91
|
-
}
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
2. **Fix Busy Wait Loop**
|
|
95
|
-
```typescript
|
|
96
|
-
// Instead of:
|
|
97
|
-
while (Date.now() < waitUntil) { }
|
|
98
|
-
|
|
99
|
-
// Use:
|
|
100
|
-
await new Promise(resolve => setTimeout(resolve, retryDelayMs));
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
3. **Add Timer Cleanup**
|
|
104
|
-
```typescript
|
|
105
|
-
class Component {
|
|
106
|
-
private cleanupTimer?: NodeJS.Timeout;
|
|
107
|
-
|
|
108
|
-
start() {
|
|
109
|
-
this.cleanupTimer = setInterval(() => { ... }, 60000);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
stop() {
|
|
113
|
-
if (this.cleanupTimer) {
|
|
114
|
-
clearInterval(this.cleanupTimer);
|
|
115
|
-
this.cleanupTimer = undefined;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### Medium Priority
|
|
122
|
-
|
|
123
|
-
1. **Optimize JSON Operations**
|
|
124
|
-
- Cache JSON.stringify results for frequently used objects
|
|
125
|
-
- Consider using faster hashing for cache keys (e.g., crypto.createHash)
|
|
126
|
-
- Use streaming JSON parsers for large objects
|
|
127
|
-
|
|
128
|
-
2. **Improve Connection Pool**
|
|
129
|
-
- Implement backpressure/queueing when pool is full
|
|
130
|
-
- Use a heap or priority queue for connection management instead of sorting
|
|
131
|
-
|
|
132
|
-
3. **Standardize Resource Cleanup**
|
|
133
|
-
- Create a base class for components with lifecycle management
|
|
134
|
-
- Ensure all event listeners are removed on cleanup
|
|
135
|
-
- Add abort controllers for better cancellation support
|
|
136
|
-
|
|
137
|
-
### Long-term Improvements
|
|
138
|
-
|
|
139
|
-
1. **Worker Threads**
|
|
140
|
-
- Move CPU-intensive operations to worker threads
|
|
141
|
-
- Consider using worker pools for NFTables operations
|
|
142
|
-
|
|
143
|
-
2. **Monitoring and Metrics**
|
|
144
|
-
- Add performance monitoring for event loop lag
|
|
145
|
-
- Track connection pool utilization
|
|
146
|
-
- Monitor memory usage patterns
|
|
147
|
-
|
|
148
|
-
3. **Graceful Degradation**
|
|
149
|
-
- Implement circuit breakers for backend connections
|
|
150
|
-
- Add request queuing with overflow protection
|
|
151
|
-
- Implement adaptive timeout strategies
|
|
152
|
-
|
|
153
|
-
## Impact Assessment
|
|
154
|
-
|
|
155
|
-
These issues primarily affect:
|
|
156
|
-
- **Scalability**: Blocking operations limit concurrent connection handling
|
|
157
|
-
- **Responsiveness**: Event loop blocking causes latency spikes
|
|
158
|
-
- **Stability**: Memory leaks could cause crashes under sustained load
|
|
159
|
-
- **Resource Usage**: Inefficient resource management increases memory/CPU usage
|
|
160
|
-
|
|
161
|
-
## Testing Recommendations
|
|
162
|
-
|
|
163
|
-
1. Load test with high connection counts (10k+ concurrent)
|
|
164
|
-
2. Monitor event loop lag under stress
|
|
165
|
-
3. Test long-running scenarios to detect memory leaks
|
|
166
|
-
4. Benchmark with async vs sync operations to measure improvement
|
|
167
|
-
|
|
168
|
-
## Conclusion
|
|
169
|
-
|
|
170
|
-
While SmartProxy has good architectural design and many best practices, the identified blocking operations and resource management issues could significantly impact performance under high load. The most critical issues (busy wait loop and synchronous filesystem operations) should be addressed immediately.
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Route matching utilities for SmartProxy components
|
|
3
|
-
*
|
|
4
|
-
* Contains shared logic for domain matching, path matching, and IP matching
|
|
5
|
-
* to be used by different proxy components throughout the system.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Match a domain pattern against a domain
|
|
10
|
-
*
|
|
11
|
-
* @param pattern Domain pattern with optional wildcards (e.g., "*.example.com")
|
|
12
|
-
* @param domain Domain to match against the pattern
|
|
13
|
-
* @returns Whether the domain matches the pattern
|
|
14
|
-
*/
|
|
15
|
-
export function matchDomain(pattern: string, domain: string): boolean {
|
|
16
|
-
// Handle exact match (case-insensitive)
|
|
17
|
-
if (pattern.toLowerCase() === domain.toLowerCase()) {
|
|
18
|
-
return true;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Handle wildcard pattern
|
|
22
|
-
if (pattern.includes('*')) {
|
|
23
|
-
const regexPattern = pattern
|
|
24
|
-
.replace(/\./g, '\\.') // Escape dots
|
|
25
|
-
.replace(/\*/g, '.*'); // Convert * to .*
|
|
26
|
-
|
|
27
|
-
const regex = new RegExp(`^${regexPattern}$`, 'i');
|
|
28
|
-
return regex.test(domain);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Match domains from a route against a given domain
|
|
36
|
-
*
|
|
37
|
-
* @param domains Array or single domain pattern to match against
|
|
38
|
-
* @param domain Domain to match
|
|
39
|
-
* @returns Whether the domain matches any of the patterns
|
|
40
|
-
*/
|
|
41
|
-
export function matchRouteDomain(domains: string | string[] | undefined, domain: string | undefined): boolean {
|
|
42
|
-
// If no domains specified in the route, match all domains
|
|
43
|
-
if (!domains) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// If no domain in the request, can't match domain-specific routes
|
|
48
|
-
if (!domain) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const patterns = Array.isArray(domains) ? domains : [domains];
|
|
53
|
-
return patterns.some(pattern => matchDomain(pattern, domain));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Match a path pattern against a path
|
|
58
|
-
*
|
|
59
|
-
* @param pattern Path pattern with optional wildcards
|
|
60
|
-
* @param path Path to match against the pattern
|
|
61
|
-
* @returns Whether the path matches the pattern
|
|
62
|
-
*/
|
|
63
|
-
export function matchPath(pattern: string, path: string): boolean {
|
|
64
|
-
// Handle exact match
|
|
65
|
-
if (pattern === path) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Handle simple wildcard at the end (like /api/*)
|
|
70
|
-
if (pattern.endsWith('*')) {
|
|
71
|
-
const prefix = pattern.slice(0, -1);
|
|
72
|
-
return path.startsWith(prefix);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Handle more complex wildcard patterns
|
|
76
|
-
if (pattern.includes('*')) {
|
|
77
|
-
const regexPattern = pattern
|
|
78
|
-
.replace(/\./g, '\\.') // Escape dots
|
|
79
|
-
.replace(/\*/g, '.*') // Convert * to .*
|
|
80
|
-
.replace(/\//g, '\\/'); // Escape slashes
|
|
81
|
-
|
|
82
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
83
|
-
return regex.test(path);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Parse CIDR notation into subnet and mask bits
|
|
91
|
-
*
|
|
92
|
-
* @param cidr CIDR string (e.g., "192.168.1.0/24")
|
|
93
|
-
* @returns Object with subnet and bits, or null if invalid
|
|
94
|
-
*/
|
|
95
|
-
export function parseCidr(cidr: string): { subnet: string; bits: number } | null {
|
|
96
|
-
try {
|
|
97
|
-
const [subnet, bitsStr] = cidr.split('/');
|
|
98
|
-
const bits = parseInt(bitsStr, 10);
|
|
99
|
-
|
|
100
|
-
if (isNaN(bits) || bits < 0 || bits > 32) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return { subnet, bits };
|
|
105
|
-
} catch (e) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Convert an IP address to a numeric value
|
|
112
|
-
*
|
|
113
|
-
* @param ip IPv4 address string (e.g., "192.168.1.1")
|
|
114
|
-
* @returns Numeric representation of the IP
|
|
115
|
-
*/
|
|
116
|
-
export function ipToNumber(ip: string): number {
|
|
117
|
-
// Handle IPv6-mapped IPv4 addresses (::ffff:192.168.1.1)
|
|
118
|
-
if (ip.startsWith('::ffff:')) {
|
|
119
|
-
ip = ip.slice(7);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const parts = ip.split('.').map(part => parseInt(part, 10));
|
|
123
|
-
return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | parts[3];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Match an IP against a CIDR pattern
|
|
128
|
-
*
|
|
129
|
-
* @param cidr CIDR pattern (e.g., "192.168.1.0/24")
|
|
130
|
-
* @param ip IP to match against the pattern
|
|
131
|
-
* @returns Whether the IP is in the CIDR range
|
|
132
|
-
*/
|
|
133
|
-
export function matchIpCidr(cidr: string, ip: string): boolean {
|
|
134
|
-
const parsed = parseCidr(cidr);
|
|
135
|
-
if (!parsed) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
const { subnet, bits } = parsed;
|
|
141
|
-
|
|
142
|
-
// Normalize IPv6-mapped IPv4 addresses
|
|
143
|
-
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
|
|
144
|
-
const normalizedSubnet = subnet.startsWith('::ffff:') ? subnet.substring(7) : subnet;
|
|
145
|
-
|
|
146
|
-
// Convert IP addresses to numeric values
|
|
147
|
-
const ipNum = ipToNumber(normalizedIp);
|
|
148
|
-
const subnetNum = ipToNumber(normalizedSubnet);
|
|
149
|
-
|
|
150
|
-
// Calculate subnet mask
|
|
151
|
-
const maskNum = ~(2 ** (32 - bits) - 1);
|
|
152
|
-
|
|
153
|
-
// Check if IP is in subnet
|
|
154
|
-
return (ipNum & maskNum) === (subnetNum & maskNum);
|
|
155
|
-
} catch (e) {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Match an IP pattern against an IP
|
|
162
|
-
*
|
|
163
|
-
* @param pattern IP pattern (exact, CIDR, or with wildcards)
|
|
164
|
-
* @param ip IP to match against the pattern
|
|
165
|
-
* @returns Whether the IP matches the pattern
|
|
166
|
-
*/
|
|
167
|
-
export function matchIpPattern(pattern: string, ip: string): boolean {
|
|
168
|
-
// Normalize IPv6-mapped IPv4 addresses
|
|
169
|
-
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
|
|
170
|
-
const normalizedPattern = pattern.startsWith('::ffff:') ? pattern.substring(7) : pattern;
|
|
171
|
-
|
|
172
|
-
// Handle exact match with all variations
|
|
173
|
-
if (pattern === ip || normalizedPattern === normalizedIp ||
|
|
174
|
-
pattern === normalizedIp || normalizedPattern === ip) {
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Handle "all" wildcard
|
|
179
|
-
if (pattern === '*' || normalizedPattern === '*') {
|
|
180
|
-
return true;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Handle CIDR notation (e.g., 192.168.1.0/24)
|
|
184
|
-
if (pattern.includes('/')) {
|
|
185
|
-
return matchIpCidr(pattern, normalizedIp) ||
|
|
186
|
-
(normalizedPattern !== pattern && matchIpCidr(normalizedPattern, normalizedIp));
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Handle glob pattern (e.g., 192.168.1.*)
|
|
190
|
-
if (pattern.includes('*')) {
|
|
191
|
-
const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
|
|
192
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
193
|
-
if (regex.test(ip) || regex.test(normalizedIp)) {
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// If pattern was normalized, also test with normalized pattern
|
|
198
|
-
if (normalizedPattern !== pattern) {
|
|
199
|
-
const normalizedRegexPattern = normalizedPattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
|
|
200
|
-
const normalizedRegex = new RegExp(`^${normalizedRegexPattern}$`);
|
|
201
|
-
return normalizedRegex.test(ip) || normalizedRegex.test(normalizedIp);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Match an IP against allowed and blocked IP patterns
|
|
210
|
-
*
|
|
211
|
-
* @param ip IP to check
|
|
212
|
-
* @param ipAllowList Array of allowed IP patterns
|
|
213
|
-
* @param ipBlockList Array of blocked IP patterns
|
|
214
|
-
* @returns Whether the IP is allowed
|
|
215
|
-
*/
|
|
216
|
-
export function isIpAuthorized(
|
|
217
|
-
ip: string,
|
|
218
|
-
ipAllowList: string[] = ['*'],
|
|
219
|
-
ipBlockList: string[] = []
|
|
220
|
-
): boolean {
|
|
221
|
-
// Check blocked IPs first
|
|
222
|
-
if (ipBlockList.length > 0) {
|
|
223
|
-
for (const pattern of ipBlockList) {
|
|
224
|
-
if (matchIpPattern(pattern, ip)) {
|
|
225
|
-
return false; // IP is blocked
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// If there are allowed IPs, check them
|
|
231
|
-
if (ipAllowList.length > 0) {
|
|
232
|
-
// Special case: if '*' is in allowed IPs, all non-blocked IPs are allowed
|
|
233
|
-
if (ipAllowList.includes('*')) {
|
|
234
|
-
return true;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
for (const pattern of ipAllowList) {
|
|
238
|
-
if (matchIpPattern(pattern, ip)) {
|
|
239
|
-
return true; // IP is allowed
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return false; // IP not in allowed list
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// No allowed IPs specified, so IP is allowed by default
|
|
246
|
-
return true;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Match an HTTP header pattern against a header value
|
|
251
|
-
*
|
|
252
|
-
* @param pattern Expected header value (string or RegExp)
|
|
253
|
-
* @param value Actual header value
|
|
254
|
-
* @returns Whether the header matches the pattern
|
|
255
|
-
*/
|
|
256
|
-
export function matchHeader(pattern: string | RegExp, value: string): boolean {
|
|
257
|
-
if (typeof pattern === 'string') {
|
|
258
|
-
return pattern === value;
|
|
259
|
-
} else if (pattern instanceof RegExp) {
|
|
260
|
-
return pattern.test(value);
|
|
261
|
-
}
|
|
262
|
-
return false;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Calculate route specificity score
|
|
267
|
-
* Higher score means more specific matching criteria
|
|
268
|
-
*
|
|
269
|
-
* @param match Match criteria to evaluate
|
|
270
|
-
* @returns Numeric specificity score
|
|
271
|
-
*/
|
|
272
|
-
export function calculateRouteSpecificity(match: {
|
|
273
|
-
domains?: string | string[];
|
|
274
|
-
path?: string;
|
|
275
|
-
clientIp?: string[];
|
|
276
|
-
tlsVersion?: string[];
|
|
277
|
-
headers?: Record<string, string | RegExp>;
|
|
278
|
-
}): number {
|
|
279
|
-
let score = 0;
|
|
280
|
-
|
|
281
|
-
// Path is very specific
|
|
282
|
-
if (match.path) {
|
|
283
|
-
// More specific if it doesn't use wildcards
|
|
284
|
-
score += match.path.includes('*') ? 3 : 4;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Domain is next most specific
|
|
288
|
-
if (match.domains) {
|
|
289
|
-
const domains = Array.isArray(match.domains) ? match.domains : [match.domains];
|
|
290
|
-
// More domains or more specific domains (without wildcards) increase specificity
|
|
291
|
-
score += domains.length;
|
|
292
|
-
// Add bonus for exact domains (without wildcards)
|
|
293
|
-
score += domains.some(d => !d.includes('*')) ? 1 : 0;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Headers are quite specific
|
|
297
|
-
if (match.headers) {
|
|
298
|
-
score += Object.keys(match.headers).length * 2;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Client IP adds some specificity
|
|
302
|
-
if (match.clientIp && match.clientIp.length > 0) {
|
|
303
|
-
score += 1;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// TLS version adds minimal specificity
|
|
307
|
-
if (match.tlsVersion && match.tlsVersion.length > 0) {
|
|
308
|
-
score += 1;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return score;
|
|
312
|
-
}
|