@push.rocks/smartproxy 3.41.8 → 4.1.0

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.
Files changed (43) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/classes.pp.acmemanager.d.ts +34 -0
  3. package/dist_ts/classes.pp.acmemanager.js +123 -0
  4. package/dist_ts/classes.pp.connectionhandler.d.ts +39 -0
  5. package/dist_ts/classes.pp.connectionhandler.js +693 -0
  6. package/dist_ts/classes.pp.connectionmanager.d.ts +78 -0
  7. package/dist_ts/classes.pp.connectionmanager.js +378 -0
  8. package/dist_ts/classes.pp.domainconfigmanager.d.ts +55 -0
  9. package/dist_ts/classes.pp.domainconfigmanager.js +103 -0
  10. package/dist_ts/classes.pp.interfaces.d.ts +109 -0
  11. package/dist_ts/classes.pp.interfaces.js +2 -0
  12. package/dist_ts/classes.pp.networkproxybridge.d.ts +43 -0
  13. package/dist_ts/classes.pp.networkproxybridge.js +211 -0
  14. package/dist_ts/classes.pp.portproxy.d.ts +48 -0
  15. package/dist_ts/classes.pp.portproxy.js +268 -0
  16. package/dist_ts/classes.pp.portrangemanager.d.ts +56 -0
  17. package/dist_ts/classes.pp.portrangemanager.js +179 -0
  18. package/dist_ts/classes.pp.securitymanager.d.ts +47 -0
  19. package/dist_ts/classes.pp.securitymanager.js +126 -0
  20. package/dist_ts/classes.pp.snihandler.d.ts +198 -0
  21. package/dist_ts/classes.pp.snihandler.js +1210 -0
  22. package/dist_ts/classes.pp.timeoutmanager.d.ts +47 -0
  23. package/dist_ts/classes.pp.timeoutmanager.js +154 -0
  24. package/dist_ts/classes.pp.tlsmanager.d.ts +57 -0
  25. package/dist_ts/classes.pp.tlsmanager.js +132 -0
  26. package/dist_ts/index.d.ts +2 -2
  27. package/dist_ts/index.js +3 -3
  28. package/package.json +1 -1
  29. package/ts/00_commitinfo_data.ts +1 -1
  30. package/ts/classes.pp.acmemanager.ts +149 -0
  31. package/ts/classes.pp.connectionhandler.ts +982 -0
  32. package/ts/classes.pp.connectionmanager.ts +446 -0
  33. package/ts/classes.pp.domainconfigmanager.ts +123 -0
  34. package/ts/classes.pp.interfaces.ts +136 -0
  35. package/ts/classes.pp.networkproxybridge.ts +258 -0
  36. package/ts/classes.pp.portproxy.ts +344 -0
  37. package/ts/classes.pp.portrangemanager.ts +214 -0
  38. package/ts/classes.pp.securitymanager.ts +147 -0
  39. package/ts/{classes.snihandler.ts → classes.pp.snihandler.ts} +1 -1
  40. package/ts/classes.pp.timeoutmanager.ts +190 -0
  41. package/ts/classes.pp.tlsmanager.ts +206 -0
  42. package/ts/index.ts +2 -2
  43. package/ts/classes.portproxy.ts +0 -2503
@@ -0,0 +1,214 @@
1
+ import type{ IPortProxySettings } from './classes.pp.interfaces.js';
2
+
3
+ /**
4
+ * Manages port ranges and port-based configuration
5
+ */
6
+ export class PortRangeManager {
7
+ constructor(private settings: IPortProxySettings) {}
8
+
9
+ /**
10
+ * Get all ports that should be listened on
11
+ */
12
+ public getListeningPorts(): Set<number> {
13
+ const listeningPorts = new Set<number>();
14
+
15
+ // Always include the main fromPort
16
+ listeningPorts.add(this.settings.fromPort);
17
+
18
+ // Add ports from global port ranges if defined
19
+ if (this.settings.globalPortRanges && this.settings.globalPortRanges.length > 0) {
20
+ for (const range of this.settings.globalPortRanges) {
21
+ for (let port = range.from; port <= range.to; port++) {
22
+ listeningPorts.add(port);
23
+ }
24
+ }
25
+ }
26
+
27
+ return listeningPorts;
28
+ }
29
+
30
+ /**
31
+ * Check if a port should use NetworkProxy for forwarding
32
+ */
33
+ public shouldUseNetworkProxy(port: number): boolean {
34
+ return !!this.settings.useNetworkProxy && this.settings.useNetworkProxy.includes(port);
35
+ }
36
+
37
+ /**
38
+ * Check if port should use global forwarding
39
+ */
40
+ public shouldUseGlobalForwarding(port: number): boolean {
41
+ return (
42
+ !!this.settings.forwardAllGlobalRanges &&
43
+ this.isPortInGlobalRanges(port)
44
+ );
45
+ }
46
+
47
+ /**
48
+ * Check if a port is in global ranges
49
+ */
50
+ public isPortInGlobalRanges(port: number): boolean {
51
+ return (
52
+ this.settings.globalPortRanges &&
53
+ this.isPortInRanges(port, this.settings.globalPortRanges)
54
+ );
55
+ }
56
+
57
+ /**
58
+ * Check if a port falls within the specified ranges
59
+ */
60
+ public isPortInRanges(port: number, ranges: Array<{ from: number; to: number }>): boolean {
61
+ return ranges.some((range) => port >= range.from && port <= range.to);
62
+ }
63
+
64
+ /**
65
+ * Get forwarding port for a specific listening port
66
+ * This determines what port to connect to on the target
67
+ */
68
+ public getForwardingPort(listeningPort: number): number {
69
+ // If using global forwarding, forward to the original port
70
+ if (this.settings.forwardAllGlobalRanges && this.isPortInGlobalRanges(listeningPort)) {
71
+ return listeningPort;
72
+ }
73
+
74
+ // Otherwise use the configured toPort
75
+ return this.settings.toPort;
76
+ }
77
+
78
+ /**
79
+ * Find domain-specific port ranges that include a given port
80
+ */
81
+ public findDomainPortRange(port: number): {
82
+ domainIndex: number,
83
+ range: { from: number, to: number }
84
+ } | undefined {
85
+ for (let i = 0; i < this.settings.domainConfigs.length; i++) {
86
+ const domain = this.settings.domainConfigs[i];
87
+ if (domain.portRanges) {
88
+ for (const range of domain.portRanges) {
89
+ if (port >= range.from && port <= range.to) {
90
+ return { domainIndex: i, range };
91
+ }
92
+ }
93
+ }
94
+ }
95
+ return undefined;
96
+ }
97
+
98
+ /**
99
+ * Get a list of all configured ports
100
+ * This includes the fromPort, NetworkProxy ports, and ports from all ranges
101
+ */
102
+ public getAllConfiguredPorts(): number[] {
103
+ const ports = new Set<number>();
104
+
105
+ // Add main listening port
106
+ ports.add(this.settings.fromPort);
107
+
108
+ // Add NetworkProxy port if configured
109
+ if (this.settings.networkProxyPort) {
110
+ ports.add(this.settings.networkProxyPort);
111
+ }
112
+
113
+ // Add NetworkProxy ports
114
+ if (this.settings.useNetworkProxy) {
115
+ for (const port of this.settings.useNetworkProxy) {
116
+ ports.add(port);
117
+ }
118
+ }
119
+
120
+ // Add ACME HTTP challenge port if enabled
121
+ if (this.settings.acme?.enabled && this.settings.acme.port) {
122
+ ports.add(this.settings.acme.port);
123
+ }
124
+
125
+ // Add global port ranges
126
+ if (this.settings.globalPortRanges) {
127
+ for (const range of this.settings.globalPortRanges) {
128
+ for (let port = range.from; port <= range.to; port++) {
129
+ ports.add(port);
130
+ }
131
+ }
132
+ }
133
+
134
+ // Add domain-specific port ranges
135
+ for (const domain of this.settings.domainConfigs) {
136
+ if (domain.portRanges) {
137
+ for (const range of domain.portRanges) {
138
+ for (let port = range.from; port <= range.to; port++) {
139
+ ports.add(port);
140
+ }
141
+ }
142
+ }
143
+
144
+ // Add domain-specific NetworkProxy port if configured
145
+ if (domain.useNetworkProxy && domain.networkProxyPort) {
146
+ ports.add(domain.networkProxyPort);
147
+ }
148
+ }
149
+
150
+ return Array.from(ports);
151
+ }
152
+
153
+ /**
154
+ * Validate port configuration
155
+ * Returns array of warning messages
156
+ */
157
+ public validateConfiguration(): string[] {
158
+ const warnings: string[] = [];
159
+
160
+ // Check for overlapping port ranges
161
+ const portMappings = new Map<number, string[]>();
162
+
163
+ // Track global port ranges
164
+ if (this.settings.globalPortRanges) {
165
+ for (const range of this.settings.globalPortRanges) {
166
+ for (let port = range.from; port <= range.to; port++) {
167
+ if (!portMappings.has(port)) {
168
+ portMappings.set(port, []);
169
+ }
170
+ portMappings.get(port)!.push('Global Port Range');
171
+ }
172
+ }
173
+ }
174
+
175
+ // Track domain-specific port ranges
176
+ for (const domain of this.settings.domainConfigs) {
177
+ if (domain.portRanges) {
178
+ for (const range of domain.portRanges) {
179
+ for (let port = range.from; port <= range.to; port++) {
180
+ if (!portMappings.has(port)) {
181
+ portMappings.set(port, []);
182
+ }
183
+ portMappings.get(port)!.push(`Domain: ${domain.domains.join(', ')}`);
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // Check for ports with multiple mappings
190
+ for (const [port, mappings] of portMappings.entries()) {
191
+ if (mappings.length > 1) {
192
+ warnings.push(`Port ${port} has multiple mappings: ${mappings.join(', ')}`);
193
+ }
194
+ }
195
+
196
+ // Check if main ports are used elsewhere
197
+ if (portMappings.has(this.settings.fromPort) && portMappings.get(this.settings.fromPort)!.length > 0) {
198
+ warnings.push(`Main listening port ${this.settings.fromPort} is also used in port ranges`);
199
+ }
200
+
201
+ if (this.settings.networkProxyPort && portMappings.has(this.settings.networkProxyPort)) {
202
+ warnings.push(`NetworkProxy port ${this.settings.networkProxyPort} is also used in port ranges`);
203
+ }
204
+
205
+ // Check ACME port
206
+ if (this.settings.acme?.enabled && this.settings.acme.port) {
207
+ if (portMappings.has(this.settings.acme.port)) {
208
+ warnings.push(`ACME HTTP challenge port ${this.settings.acme.port} is also used in port ranges`);
209
+ }
210
+ }
211
+
212
+ return warnings;
213
+ }
214
+ }
@@ -0,0 +1,147 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IPortProxySettings } from './classes.pp.interfaces.js';
3
+
4
+ /**
5
+ * Handles security aspects like IP tracking, rate limiting, and authorization
6
+ */
7
+ export class SecurityManager {
8
+ private connectionsByIP: Map<string, Set<string>> = new Map();
9
+ private connectionRateByIP: Map<string, number[]> = new Map();
10
+
11
+ constructor(private settings: IPortProxySettings) {}
12
+
13
+ /**
14
+ * Get connections count by IP
15
+ */
16
+ public getConnectionCountByIP(ip: string): number {
17
+ return this.connectionsByIP.get(ip)?.size || 0;
18
+ }
19
+
20
+ /**
21
+ * Check and update connection rate for an IP
22
+ * @returns true if within rate limit, false if exceeding limit
23
+ */
24
+ public checkConnectionRate(ip: string): boolean {
25
+ const now = Date.now();
26
+ const minute = 60 * 1000;
27
+
28
+ if (!this.connectionRateByIP.has(ip)) {
29
+ this.connectionRateByIP.set(ip, [now]);
30
+ return true;
31
+ }
32
+
33
+ // Get timestamps and filter out entries older than 1 minute
34
+ const timestamps = this.connectionRateByIP.get(ip)!.filter((time) => now - time < minute);
35
+ timestamps.push(now);
36
+ this.connectionRateByIP.set(ip, timestamps);
37
+
38
+ // Check if rate exceeds limit
39
+ return timestamps.length <= this.settings.connectionRateLimitPerMinute!;
40
+ }
41
+
42
+ /**
43
+ * Track connection by IP
44
+ */
45
+ public trackConnectionByIP(ip: string, connectionId: string): void {
46
+ if (!this.connectionsByIP.has(ip)) {
47
+ this.connectionsByIP.set(ip, new Set());
48
+ }
49
+ this.connectionsByIP.get(ip)!.add(connectionId);
50
+ }
51
+
52
+ /**
53
+ * Remove connection tracking for an IP
54
+ */
55
+ public removeConnectionByIP(ip: string, connectionId: string): void {
56
+ if (this.connectionsByIP.has(ip)) {
57
+ const connections = this.connectionsByIP.get(ip)!;
58
+ connections.delete(connectionId);
59
+ if (connections.size === 0) {
60
+ this.connectionsByIP.delete(ip);
61
+ }
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Check if an IP is allowed using glob patterns
67
+ */
68
+ public isIPAuthorized(ip: string, allowedIPs: string[], blockedIPs: string[] = []): boolean {
69
+ // Skip IP validation if allowedIPs is empty
70
+ if (!ip || (allowedIPs.length === 0 && blockedIPs.length === 0)) {
71
+ return true;
72
+ }
73
+
74
+ // First check if IP is blocked
75
+ if (blockedIPs.length > 0 && this.isGlobIPMatch(ip, blockedIPs)) {
76
+ return false;
77
+ }
78
+
79
+ // Then check if IP is allowed
80
+ return this.isGlobIPMatch(ip, allowedIPs);
81
+ }
82
+
83
+ /**
84
+ * Check if the IP matches any of the glob patterns
85
+ */
86
+ private isGlobIPMatch(ip: string, patterns: string[]): boolean {
87
+ if (!ip || !patterns || patterns.length === 0) return false;
88
+
89
+ const normalizeIP = (ip: string): string[] => {
90
+ if (!ip) return [];
91
+ if (ip.startsWith('::ffff:')) {
92
+ const ipv4 = ip.slice(7);
93
+ return [ip, ipv4];
94
+ }
95
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
96
+ return [ip, `::ffff:${ip}`];
97
+ }
98
+ return [ip];
99
+ };
100
+
101
+ const normalizedIPVariants = normalizeIP(ip);
102
+ if (normalizedIPVariants.length === 0) return false;
103
+
104
+ const expandedPatterns = patterns.flatMap(normalizeIP);
105
+ return normalizedIPVariants.some((ipVariant) =>
106
+ expandedPatterns.some((pattern) => plugins.minimatch(ipVariant, pattern))
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Check if IP should be allowed considering connection rate and max connections
112
+ * @returns Object with result and reason
113
+ */
114
+ public validateIP(ip: string): { allowed: boolean; reason?: string } {
115
+ // Check connection count limit
116
+ if (
117
+ this.settings.maxConnectionsPerIP &&
118
+ this.getConnectionCountByIP(ip) >= this.settings.maxConnectionsPerIP
119
+ ) {
120
+ return {
121
+ allowed: false,
122
+ reason: `Maximum connections per IP (${this.settings.maxConnectionsPerIP}) exceeded`
123
+ };
124
+ }
125
+
126
+ // Check connection rate limit
127
+ if (
128
+ this.settings.connectionRateLimitPerMinute &&
129
+ !this.checkConnectionRate(ip)
130
+ ) {
131
+ return {
132
+ allowed: false,
133
+ reason: `Connection rate limit (${this.settings.connectionRateLimitPerMinute}/min) exceeded`
134
+ };
135
+ }
136
+
137
+ return { allowed: true };
138
+ }
139
+
140
+ /**
141
+ * Clears all IP tracking data (for shutdown)
142
+ */
143
+ public clearIPTracking(): void {
144
+ this.connectionsByIP.clear();
145
+ this.connectionRateByIP.clear();
146
+ }
147
+ }
@@ -1466,4 +1466,4 @@ export class SniHandler {
1466
1466
 
1467
1467
  return undefined;
1468
1468
  }
1469
- }
1469
+ }
@@ -0,0 +1,190 @@
1
+ import type { IConnectionRecord, IPortProxySettings } from './classes.pp.interfaces.js';
2
+
3
+ /**
4
+ * Manages timeouts and inactivity tracking for connections
5
+ */
6
+ export class TimeoutManager {
7
+ constructor(private settings: IPortProxySettings) {}
8
+
9
+ /**
10
+ * Ensure timeout values don't exceed Node.js max safe integer
11
+ */
12
+ public ensureSafeTimeout(timeout: number): number {
13
+ const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
14
+ return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
15
+ }
16
+
17
+ /**
18
+ * Generate a slightly randomized timeout to prevent thundering herd
19
+ */
20
+ public randomizeTimeout(baseTimeout: number, variationPercent: number = 5): number {
21
+ const safeBaseTimeout = this.ensureSafeTimeout(baseTimeout);
22
+ const variation = safeBaseTimeout * (variationPercent / 100);
23
+ return this.ensureSafeTimeout(
24
+ safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation
25
+ );
26
+ }
27
+
28
+ /**
29
+ * Update connection activity timestamp
30
+ */
31
+ public updateActivity(record: IConnectionRecord): void {
32
+ record.lastActivity = Date.now();
33
+
34
+ // Clear any inactivity warning
35
+ if (record.inactivityWarningIssued) {
36
+ record.inactivityWarningIssued = false;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Calculate effective inactivity timeout based on connection type
42
+ */
43
+ public getEffectiveInactivityTimeout(record: IConnectionRecord): number {
44
+ let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
45
+
46
+ // For immortal keep-alive connections, use an extremely long timeout
47
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
48
+ return Number.MAX_SAFE_INTEGER;
49
+ }
50
+
51
+ // For extended keep-alive connections, apply multiplier
52
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
53
+ const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
54
+ effectiveTimeout = effectiveTimeout * multiplier;
55
+ }
56
+
57
+ return this.ensureSafeTimeout(effectiveTimeout);
58
+ }
59
+
60
+ /**
61
+ * Calculate effective max lifetime based on connection type
62
+ */
63
+ public getEffectiveMaxLifetime(record: IConnectionRecord): number {
64
+ // Use domain-specific timeout if available
65
+ const baseTimeout = record.domainConfig?.connectionTimeout ||
66
+ this.settings.maxConnectionLifetime ||
67
+ 86400000; // 24 hours default
68
+
69
+ // For immortal keep-alive connections, use an extremely long lifetime
70
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
71
+ return Number.MAX_SAFE_INTEGER;
72
+ }
73
+
74
+ // For extended keep-alive connections, use the extended lifetime setting
75
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
76
+ return this.ensureSafeTimeout(
77
+ this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
78
+ );
79
+ }
80
+
81
+ // Apply randomization if enabled
82
+ if (this.settings.enableRandomizedTimeouts) {
83
+ return this.randomizeTimeout(baseTimeout);
84
+ }
85
+
86
+ return this.ensureSafeTimeout(baseTimeout);
87
+ }
88
+
89
+ /**
90
+ * Setup connection timeout
91
+ * @returns The cleanup timer
92
+ */
93
+ public setupConnectionTimeout(
94
+ record: IConnectionRecord,
95
+ onTimeout: (record: IConnectionRecord, reason: string) => void
96
+ ): NodeJS.Timeout {
97
+ // Clear any existing timer
98
+ if (record.cleanupTimer) {
99
+ clearTimeout(record.cleanupTimer);
100
+ }
101
+
102
+ // Calculate effective timeout
103
+ const effectiveLifetime = this.getEffectiveMaxLifetime(record);
104
+
105
+ // Set up the timeout
106
+ const timer = setTimeout(() => {
107
+ // Call the provided callback
108
+ onTimeout(record, 'connection_timeout');
109
+ }, effectiveLifetime);
110
+
111
+ // Make sure timeout doesn't keep the process alive
112
+ if (timer.unref) {
113
+ timer.unref();
114
+ }
115
+
116
+ return timer;
117
+ }
118
+
119
+ /**
120
+ * Check for inactivity on a connection
121
+ * @returns Object with check results
122
+ */
123
+ public checkInactivity(record: IConnectionRecord): {
124
+ isInactive: boolean;
125
+ shouldWarn: boolean;
126
+ inactivityTime: number;
127
+ effectiveTimeout: number;
128
+ } {
129
+ // Skip for connections with inactivity check disabled
130
+ if (this.settings.disableInactivityCheck) {
131
+ return {
132
+ isInactive: false,
133
+ shouldWarn: false,
134
+ inactivityTime: 0,
135
+ effectiveTimeout: 0
136
+ };
137
+ }
138
+
139
+ // Skip for immortal keep-alive connections
140
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
141
+ return {
142
+ isInactive: false,
143
+ shouldWarn: false,
144
+ inactivityTime: 0,
145
+ effectiveTimeout: 0
146
+ };
147
+ }
148
+
149
+ const now = Date.now();
150
+ const inactivityTime = now - record.lastActivity;
151
+ const effectiveTimeout = this.getEffectiveInactivityTimeout(record);
152
+
153
+ // Check if inactive
154
+ const isInactive = inactivityTime > effectiveTimeout;
155
+
156
+ // For keep-alive connections, we should warn first
157
+ const shouldWarn = record.hasKeepAlive &&
158
+ isInactive &&
159
+ !record.inactivityWarningIssued;
160
+
161
+ return {
162
+ isInactive,
163
+ shouldWarn,
164
+ inactivityTime,
165
+ effectiveTimeout
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Apply socket timeout settings
171
+ */
172
+ public applySocketTimeouts(record: IConnectionRecord): void {
173
+ // Skip for immortal keep-alive connections
174
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
175
+ // Disable timeouts completely for immortal connections
176
+ record.incoming.setTimeout(0);
177
+ if (record.outgoing) {
178
+ record.outgoing.setTimeout(0);
179
+ }
180
+ return;
181
+ }
182
+
183
+ // Apply normal timeouts
184
+ const timeout = this.ensureSafeTimeout(this.settings.socketTimeout || 3600000); // 1 hour default
185
+ record.incoming.setTimeout(timeout);
186
+ if (record.outgoing) {
187
+ record.outgoing.setTimeout(timeout);
188
+ }
189
+ }
190
+ }