@push.rocks/smartproxy 3.41.8 → 4.0.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 +160 -0
  21. package/dist_ts/classes.pp.snihandler.js +1073 -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} +2 -169
  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,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
+ }
@@ -0,0 +1,206 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IPortProxySettings } from './classes.pp.interfaces.js';
3
+ import { SniHandler } from './classes.pp.snihandler.js';
4
+
5
+ /**
6
+ * Interface for connection information used for SNI extraction
7
+ */
8
+ interface IConnectionInfo {
9
+ sourceIp: string;
10
+ sourcePort: number;
11
+ destIp: string;
12
+ destPort: number;
13
+ }
14
+
15
+ /**
16
+ * Manages TLS-related operations including SNI extraction and validation
17
+ */
18
+ export class TlsManager {
19
+ constructor(private settings: IPortProxySettings) {}
20
+
21
+ /**
22
+ * Check if a data chunk appears to be a TLS handshake
23
+ */
24
+ public isTlsHandshake(chunk: Buffer): boolean {
25
+ return SniHandler.isTlsHandshake(chunk);
26
+ }
27
+
28
+ /**
29
+ * Check if a data chunk appears to be a TLS ClientHello
30
+ */
31
+ public isClientHello(chunk: Buffer): boolean {
32
+ return SniHandler.isClientHello(chunk);
33
+ }
34
+
35
+ /**
36
+ * Extract Server Name Indication (SNI) from TLS handshake
37
+ */
38
+ public extractSNI(
39
+ chunk: Buffer,
40
+ connInfo: IConnectionInfo,
41
+ previousDomain?: string
42
+ ): string | undefined {
43
+ // Use the SniHandler to process the TLS packet
44
+ return SniHandler.processTlsPacket(
45
+ chunk,
46
+ connInfo,
47
+ this.settings.enableTlsDebugLogging || false,
48
+ previousDomain
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Handle session resumption attempts
54
+ */
55
+ public handleSessionResumption(
56
+ chunk: Buffer,
57
+ connectionId: string,
58
+ hasSNI: boolean
59
+ ): { shouldBlock: boolean; reason?: string } {
60
+ // Skip if session tickets are allowed
61
+ if (this.settings.allowSessionTicket !== false) {
62
+ return { shouldBlock: false };
63
+ }
64
+
65
+ // Check for session resumption attempt
66
+ const resumptionInfo = SniHandler.hasSessionResumption(
67
+ chunk,
68
+ this.settings.enableTlsDebugLogging || false
69
+ );
70
+
71
+ // If this is a resumption attempt without SNI, block it
72
+ if (resumptionInfo.isResumption && !hasSNI && !resumptionInfo.hasSNI) {
73
+ if (this.settings.enableTlsDebugLogging) {
74
+ console.log(
75
+ `[${connectionId}] Session resumption detected without SNI and allowSessionTicket=false. ` +
76
+ `Terminating connection to force new TLS handshake.`
77
+ );
78
+ }
79
+ return {
80
+ shouldBlock: true,
81
+ reason: 'session_ticket_blocked'
82
+ };
83
+ }
84
+
85
+ return { shouldBlock: false };
86
+ }
87
+
88
+ /**
89
+ * Check for SNI mismatch during renegotiation
90
+ */
91
+ public checkRenegotiationSNI(
92
+ chunk: Buffer,
93
+ connInfo: IConnectionInfo,
94
+ expectedDomain: string,
95
+ connectionId: string
96
+ ): { hasMismatch: boolean; extractedSNI?: string } {
97
+ // Only process if this looks like a TLS ClientHello
98
+ if (!this.isClientHello(chunk)) {
99
+ return { hasMismatch: false };
100
+ }
101
+
102
+ try {
103
+ // Extract SNI with renegotiation support
104
+ const newSNI = SniHandler.extractSNIWithResumptionSupport(
105
+ chunk,
106
+ connInfo,
107
+ this.settings.enableTlsDebugLogging || false
108
+ );
109
+
110
+ // Skip if no SNI was found
111
+ if (!newSNI) return { hasMismatch: false };
112
+
113
+ // Check for SNI mismatch
114
+ if (newSNI !== expectedDomain) {
115
+ if (this.settings.enableTlsDebugLogging) {
116
+ console.log(
117
+ `[${connectionId}] Renegotiation with different SNI: ${expectedDomain} -> ${newSNI}. ` +
118
+ `Terminating connection - SNI domain switching is not allowed.`
119
+ );
120
+ }
121
+ return { hasMismatch: true, extractedSNI: newSNI };
122
+ } else if (this.settings.enableTlsDebugLogging) {
123
+ console.log(
124
+ `[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`
125
+ );
126
+ }
127
+ } catch (err) {
128
+ console.log(
129
+ `[${connectionId}] Error processing ClientHello: ${err}. Allowing connection to continue.`
130
+ );
131
+ }
132
+
133
+ return { hasMismatch: false };
134
+ }
135
+
136
+ /**
137
+ * Create a renegotiation handler function for a connection
138
+ */
139
+ public createRenegotiationHandler(
140
+ connectionId: string,
141
+ lockedDomain: string,
142
+ connInfo: IConnectionInfo,
143
+ onMismatch: (connectionId: string, reason: string) => void
144
+ ): (chunk: Buffer) => void {
145
+ return (chunk: Buffer) => {
146
+ const result = this.checkRenegotiationSNI(chunk, connInfo, lockedDomain, connectionId);
147
+ if (result.hasMismatch) {
148
+ onMismatch(connectionId, 'sni_mismatch');
149
+ }
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Analyze TLS connection for browser fingerprinting
155
+ * This helps identify browser vs non-browser connections
156
+ */
157
+ public analyzeClientHello(chunk: Buffer): {
158
+ isBrowserConnection: boolean;
159
+ isRenewal: boolean;
160
+ hasSNI: boolean;
161
+ } {
162
+ // Default result
163
+ const result = {
164
+ isBrowserConnection: false,
165
+ isRenewal: false,
166
+ hasSNI: false
167
+ };
168
+
169
+ try {
170
+ // Check if it's a ClientHello
171
+ if (!this.isClientHello(chunk)) {
172
+ return result;
173
+ }
174
+
175
+ // Check for session resumption
176
+ const resumptionInfo = SniHandler.hasSessionResumption(
177
+ chunk,
178
+ this.settings.enableTlsDebugLogging || false
179
+ );
180
+
181
+ // Extract SNI
182
+ const sni = SniHandler.extractSNI(
183
+ chunk,
184
+ this.settings.enableTlsDebugLogging || false
185
+ );
186
+
187
+ // Update result
188
+ result.isRenewal = resumptionInfo.isResumption;
189
+ result.hasSNI = !!sni;
190
+
191
+ // Browsers typically:
192
+ // 1. Send SNI extension
193
+ // 2. Have a variety of extensions (ALPN, etc.)
194
+ // 3. Use standard cipher suites
195
+ // ...more complex heuristics could be implemented here
196
+
197
+ // Simple heuristic: presence of SNI suggests browser
198
+ result.isBrowserConnection = !!sni;
199
+
200
+ return result;
201
+ } catch (err) {
202
+ console.log(`Error analyzing ClientHello: ${err}`);
203
+ return result;
204
+ }
205
+ }
206
+ }
package/ts/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './classes.iptablesproxy.js';
2
2
  export * from './classes.networkproxy.js';
3
- export * from './classes.portproxy.js';
3
+ export * from './classes.pp.portproxy.js';
4
4
  export * from './classes.port80handler.js';
5
5
  export * from './classes.sslredirect.js';
6
- export * from './classes.snihandler.js';
6
+ export * from './classes.pp.snihandler.js';