@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,47 @@
1
+ import type { IConnectionRecord, IPortProxySettings } from './classes.pp.interfaces.js';
2
+ /**
3
+ * Manages timeouts and inactivity tracking for connections
4
+ */
5
+ export declare class TimeoutManager {
6
+ private settings;
7
+ constructor(settings: IPortProxySettings);
8
+ /**
9
+ * Ensure timeout values don't exceed Node.js max safe integer
10
+ */
11
+ ensureSafeTimeout(timeout: number): number;
12
+ /**
13
+ * Generate a slightly randomized timeout to prevent thundering herd
14
+ */
15
+ randomizeTimeout(baseTimeout: number, variationPercent?: number): number;
16
+ /**
17
+ * Update connection activity timestamp
18
+ */
19
+ updateActivity(record: IConnectionRecord): void;
20
+ /**
21
+ * Calculate effective inactivity timeout based on connection type
22
+ */
23
+ getEffectiveInactivityTimeout(record: IConnectionRecord): number;
24
+ /**
25
+ * Calculate effective max lifetime based on connection type
26
+ */
27
+ getEffectiveMaxLifetime(record: IConnectionRecord): number;
28
+ /**
29
+ * Setup connection timeout
30
+ * @returns The cleanup timer
31
+ */
32
+ setupConnectionTimeout(record: IConnectionRecord, onTimeout: (record: IConnectionRecord, reason: string) => void): NodeJS.Timeout;
33
+ /**
34
+ * Check for inactivity on a connection
35
+ * @returns Object with check results
36
+ */
37
+ checkInactivity(record: IConnectionRecord): {
38
+ isInactive: boolean;
39
+ shouldWarn: boolean;
40
+ inactivityTime: number;
41
+ effectiveTimeout: number;
42
+ };
43
+ /**
44
+ * Apply socket timeout settings
45
+ */
46
+ applySocketTimeouts(record: IConnectionRecord): void;
47
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Manages timeouts and inactivity tracking for connections
3
+ */
4
+ export class TimeoutManager {
5
+ constructor(settings) {
6
+ this.settings = settings;
7
+ }
8
+ /**
9
+ * Ensure timeout values don't exceed Node.js max safe integer
10
+ */
11
+ ensureSafeTimeout(timeout) {
12
+ const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
13
+ return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
14
+ }
15
+ /**
16
+ * Generate a slightly randomized timeout to prevent thundering herd
17
+ */
18
+ randomizeTimeout(baseTimeout, variationPercent = 5) {
19
+ const safeBaseTimeout = this.ensureSafeTimeout(baseTimeout);
20
+ const variation = safeBaseTimeout * (variationPercent / 100);
21
+ return this.ensureSafeTimeout(safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation);
22
+ }
23
+ /**
24
+ * Update connection activity timestamp
25
+ */
26
+ updateActivity(record) {
27
+ record.lastActivity = Date.now();
28
+ // Clear any inactivity warning
29
+ if (record.inactivityWarningIssued) {
30
+ record.inactivityWarningIssued = false;
31
+ }
32
+ }
33
+ /**
34
+ * Calculate effective inactivity timeout based on connection type
35
+ */
36
+ getEffectiveInactivityTimeout(record) {
37
+ let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
38
+ // For immortal keep-alive connections, use an extremely long timeout
39
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
40
+ return Number.MAX_SAFE_INTEGER;
41
+ }
42
+ // For extended keep-alive connections, apply multiplier
43
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
44
+ const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
45
+ effectiveTimeout = effectiveTimeout * multiplier;
46
+ }
47
+ return this.ensureSafeTimeout(effectiveTimeout);
48
+ }
49
+ /**
50
+ * Calculate effective max lifetime based on connection type
51
+ */
52
+ getEffectiveMaxLifetime(record) {
53
+ // Use domain-specific timeout if available
54
+ const baseTimeout = record.domainConfig?.connectionTimeout ||
55
+ this.settings.maxConnectionLifetime ||
56
+ 86400000; // 24 hours default
57
+ // For immortal keep-alive connections, use an extremely long lifetime
58
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
59
+ return Number.MAX_SAFE_INTEGER;
60
+ }
61
+ // For extended keep-alive connections, use the extended lifetime setting
62
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
63
+ return this.ensureSafeTimeout(this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000 // 7 days default
64
+ );
65
+ }
66
+ // Apply randomization if enabled
67
+ if (this.settings.enableRandomizedTimeouts) {
68
+ return this.randomizeTimeout(baseTimeout);
69
+ }
70
+ return this.ensureSafeTimeout(baseTimeout);
71
+ }
72
+ /**
73
+ * Setup connection timeout
74
+ * @returns The cleanup timer
75
+ */
76
+ setupConnectionTimeout(record, onTimeout) {
77
+ // Clear any existing timer
78
+ if (record.cleanupTimer) {
79
+ clearTimeout(record.cleanupTimer);
80
+ }
81
+ // Calculate effective timeout
82
+ const effectiveLifetime = this.getEffectiveMaxLifetime(record);
83
+ // Set up the timeout
84
+ const timer = setTimeout(() => {
85
+ // Call the provided callback
86
+ onTimeout(record, 'connection_timeout');
87
+ }, effectiveLifetime);
88
+ // Make sure timeout doesn't keep the process alive
89
+ if (timer.unref) {
90
+ timer.unref();
91
+ }
92
+ return timer;
93
+ }
94
+ /**
95
+ * Check for inactivity on a connection
96
+ * @returns Object with check results
97
+ */
98
+ checkInactivity(record) {
99
+ // Skip for connections with inactivity check disabled
100
+ if (this.settings.disableInactivityCheck) {
101
+ return {
102
+ isInactive: false,
103
+ shouldWarn: false,
104
+ inactivityTime: 0,
105
+ effectiveTimeout: 0
106
+ };
107
+ }
108
+ // Skip for immortal keep-alive connections
109
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
110
+ return {
111
+ isInactive: false,
112
+ shouldWarn: false,
113
+ inactivityTime: 0,
114
+ effectiveTimeout: 0
115
+ };
116
+ }
117
+ const now = Date.now();
118
+ const inactivityTime = now - record.lastActivity;
119
+ const effectiveTimeout = this.getEffectiveInactivityTimeout(record);
120
+ // Check if inactive
121
+ const isInactive = inactivityTime > effectiveTimeout;
122
+ // For keep-alive connections, we should warn first
123
+ const shouldWarn = record.hasKeepAlive &&
124
+ isInactive &&
125
+ !record.inactivityWarningIssued;
126
+ return {
127
+ isInactive,
128
+ shouldWarn,
129
+ inactivityTime,
130
+ effectiveTimeout
131
+ };
132
+ }
133
+ /**
134
+ * Apply socket timeout settings
135
+ */
136
+ applySocketTimeouts(record) {
137
+ // Skip for immortal keep-alive connections
138
+ if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
139
+ // Disable timeouts completely for immortal connections
140
+ record.incoming.setTimeout(0);
141
+ if (record.outgoing) {
142
+ record.outgoing.setTimeout(0);
143
+ }
144
+ return;
145
+ }
146
+ // Apply normal timeouts
147
+ const timeout = this.ensureSafeTimeout(this.settings.socketTimeout || 3600000); // 1 hour default
148
+ record.incoming.setTimeout(timeout);
149
+ if (record.outgoing) {
150
+ record.outgoing.setTimeout(timeout);
151
+ }
152
+ }
153
+ }
154
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC50aW1lb3V0bWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucHAudGltZW91dG1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUN6QixZQUFvQixRQUE0QjtRQUE1QixhQUFRLEdBQVIsUUFBUSxDQUFvQjtJQUFHLENBQUM7SUFFcEQ7O09BRUc7SUFDSSxpQkFBaUIsQ0FBQyxPQUFlO1FBQ3RDLE1BQU0sZ0JBQWdCLEdBQUcsVUFBVSxDQUFDLENBQUMsZ0NBQWdDO1FBQ3JFLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxFQUFFLGdCQUFnQixDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksZ0JBQWdCLENBQUMsV0FBbUIsRUFBRSxtQkFBMkIsQ0FBQztRQUN2RSxNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDNUQsTUFBTSxTQUFTLEdBQUcsZUFBZSxHQUFHLENBQUMsZ0JBQWdCLEdBQUcsR0FBRyxDQUFDLENBQUM7UUFDN0QsT0FBTyxJQUFJLENBQUMsaUJBQWlCLENBQzNCLGVBQWUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxTQUFTLEdBQUcsQ0FBQyxDQUFDLEdBQUcsU0FBUyxDQUN4RSxDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksY0FBYyxDQUFDLE1BQXlCO1FBQzdDLE1BQU0sQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRWpDLCtCQUErQjtRQUMvQixJQUFJLE1BQU0sQ0FBQyx1QkFBdUIsRUFBRSxDQUFDO1lBQ25DLE1BQU0sQ0FBQyx1QkFBdUIsR0FBRyxLQUFLLENBQUM7UUFDekMsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNJLDZCQUE2QixDQUFDLE1BQXlCO1FBQzVELElBQUksZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxpQkFBaUIsSUFBSSxRQUFRLENBQUMsQ0FBQyxrQkFBa0I7UUFFdEYscUVBQXFFO1FBQ3JFLElBQUksTUFBTSxDQUFDLFlBQVksSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQzNFLE9BQU8sTUFBTSxDQUFDLGdCQUFnQixDQUFDO1FBQ2pDLENBQUM7UUFFRCx3REFBd0Q7UUFDeEQsSUFBSSxNQUFNLENBQUMsWUFBWSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEtBQUssVUFBVSxFQUFFLENBQUM7WUFDM0UsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyw2QkFBNkIsSUFBSSxDQUFDLENBQUM7WUFDcEUsZ0JBQWdCLEdBQUcsZ0JBQWdCLEdBQUcsVUFBVSxDQUFDO1FBQ25ELENBQUM7UUFFRCxPQUFPLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBQ2xELENBQUM7SUFFRDs7T0FFRztJQUNJLHVCQUF1QixDQUFDLE1BQXlCO1FBQ3RELDJDQUEyQztRQUMzQyxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsWUFBWSxFQUFFLGlCQUFpQjtZQUN0QyxJQUFJLENBQUMsUUFBUSxDQUFDLHFCQUFxQjtZQUNuQyxRQUFRLENBQUMsQ0FBQyxtQkFBbUI7UUFFakQsc0VBQXNFO1FBQ3RFLElBQUksTUFBTSxDQUFDLFlBQVksSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQzNFLE9BQU8sTUFBTSxDQUFDLGdCQUFnQixDQUFDO1FBQ2pDLENBQUM7UUFFRCx5RUFBeUU7UUFDekUsSUFBSSxNQUFNLENBQUMsWUFBWSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsa0JBQWtCLEtBQUssVUFBVSxFQUFFLENBQUM7WUFDM0UsT0FBTyxJQUFJLENBQUMsaUJBQWlCLENBQzNCLElBQUksQ0FBQyxRQUFRLENBQUMseUJBQXlCLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxpQkFBaUI7YUFDckYsQ0FBQztRQUNKLENBQUM7UUFFRCxpQ0FBaUM7UUFDakMsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLHdCQUF3QixFQUFFLENBQUM7WUFDM0MsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDNUMsQ0FBQztRQUVELE9BQU8sSUFBSSxDQUFDLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQzdDLENBQUM7SUFFRDs7O09BR0c7SUFDSSxzQkFBc0IsQ0FDM0IsTUFBeUIsRUFDekIsU0FBOEQ7UUFFOUQsMkJBQTJCO1FBQzNCLElBQUksTUFBTSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3hCLFlBQVksQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDcEMsQ0FBQztRQUVELDhCQUE4QjtRQUM5QixNQUFNLGlCQUFpQixHQUFHLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUUvRCxxQkFBcUI7UUFDckIsTUFBTSxLQUFLLEdBQUcsVUFBVSxDQUFDLEdBQUcsRUFBRTtZQUM1Qiw2QkFBNkI7WUFDN0IsU0FBUyxDQUFDLE1BQU0sRUFBRSxvQkFBb0IsQ0FBQyxDQUFDO1FBQzFDLENBQUMsRUFBRSxpQkFBaUIsQ0FBQyxDQUFDO1FBRXRCLG1EQUFtRDtRQUNuRCxJQUFJLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNoQixLQUFLLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDaEIsQ0FBQztRQUVELE9BQU8sS0FBSyxDQUFDO0lBQ2YsQ0FBQztJQUVEOzs7T0FHRztJQUNJLGVBQWUsQ0FBQyxNQUF5QjtRQU05QyxzREFBc0Q7UUFDdEQsSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLHNCQUFzQixFQUFFLENBQUM7WUFDekMsT0FBTztnQkFDTCxVQUFVLEVBQUUsS0FBSztnQkFDakIsVUFBVSxFQUFFLEtBQUs7Z0JBQ2pCLGNBQWMsRUFBRSxDQUFDO2dCQUNqQixnQkFBZ0IsRUFBRSxDQUFDO2FBQ3BCLENBQUM7UUFDSixDQUFDO1FBRUQsMkNBQTJDO1FBQzNDLElBQUksTUFBTSxDQUFDLFlBQVksSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQzNFLE9BQU87Z0JBQ0wsVUFBVSxFQUFFLEtBQUs7Z0JBQ2pCLFVBQVUsRUFBRSxLQUFLO2dCQUNqQixjQUFjLEVBQUUsQ0FBQztnQkFDakIsZ0JBQWdCLEVBQUUsQ0FBQzthQUNwQixDQUFDO1FBQ0osQ0FBQztRQUVELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQUN2QixNQUFNLGNBQWMsR0FBRyxHQUFHLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQztRQUNqRCxNQUFNLGdCQUFnQixHQUFHLElBQUksQ0FBQyw2QkFBNkIsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUVwRSxvQkFBb0I7UUFDcEIsTUFBTSxVQUFVLEdBQUcsY0FBYyxHQUFHLGdCQUFnQixDQUFDO1FBRXJELG1EQUFtRDtRQUNuRCxNQUFNLFVBQVUsR0FBRyxNQUFNLENBQUMsWUFBWTtZQUNuQixVQUFVO1lBQ1YsQ0FBQyxNQUFNLENBQUMsdUJBQXVCLENBQUM7UUFFbkQsT0FBTztZQUNMLFVBQVU7WUFDVixVQUFVO1lBQ1YsY0FBYztZQUNkLGdCQUFnQjtTQUNqQixDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksbUJBQW1CLENBQUMsTUFBeUI7UUFDbEQsMkNBQTJDO1FBQzNDLElBQUksTUFBTSxDQUFDLFlBQVksSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLGtCQUFrQixLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQzNFLHVEQUF1RDtZQUN2RCxNQUFNLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM5QixJQUFJLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQztnQkFDcEIsTUFBTSxDQUFDLFFBQVEsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDaEMsQ0FBQztZQUNELE9BQU87UUFDVCxDQUFDO1FBRUQsd0JBQXdCO1FBQ3hCLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLGFBQWEsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLGlCQUFpQjtRQUNqRyxNQUFNLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNwQyxJQUFJLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNwQixNQUFNLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN0QyxDQUFDO0lBQ0gsQ0FBQztDQUNGIn0=
@@ -0,0 +1,57 @@
1
+ import type { IPortProxySettings } from './classes.pp.interfaces.js';
2
+ /**
3
+ * Interface for connection information used for SNI extraction
4
+ */
5
+ interface IConnectionInfo {
6
+ sourceIp: string;
7
+ sourcePort: number;
8
+ destIp: string;
9
+ destPort: number;
10
+ }
11
+ /**
12
+ * Manages TLS-related operations including SNI extraction and validation
13
+ */
14
+ export declare class TlsManager {
15
+ private settings;
16
+ constructor(settings: IPortProxySettings);
17
+ /**
18
+ * Check if a data chunk appears to be a TLS handshake
19
+ */
20
+ isTlsHandshake(chunk: Buffer): boolean;
21
+ /**
22
+ * Check if a data chunk appears to be a TLS ClientHello
23
+ */
24
+ isClientHello(chunk: Buffer): boolean;
25
+ /**
26
+ * Extract Server Name Indication (SNI) from TLS handshake
27
+ */
28
+ extractSNI(chunk: Buffer, connInfo: IConnectionInfo, previousDomain?: string): string | undefined;
29
+ /**
30
+ * Handle session resumption attempts
31
+ */
32
+ handleSessionResumption(chunk: Buffer, connectionId: string, hasSNI: boolean): {
33
+ shouldBlock: boolean;
34
+ reason?: string;
35
+ };
36
+ /**
37
+ * Check for SNI mismatch during renegotiation
38
+ */
39
+ checkRenegotiationSNI(chunk: Buffer, connInfo: IConnectionInfo, expectedDomain: string, connectionId: string): {
40
+ hasMismatch: boolean;
41
+ extractedSNI?: string;
42
+ };
43
+ /**
44
+ * Create a renegotiation handler function for a connection
45
+ */
46
+ createRenegotiationHandler(connectionId: string, lockedDomain: string, connInfo: IConnectionInfo, onMismatch: (connectionId: string, reason: string) => void): (chunk: Buffer) => void;
47
+ /**
48
+ * Analyze TLS connection for browser fingerprinting
49
+ * This helps identify browser vs non-browser connections
50
+ */
51
+ analyzeClientHello(chunk: Buffer): {
52
+ isBrowserConnection: boolean;
53
+ isRenewal: boolean;
54
+ hasSNI: boolean;
55
+ };
56
+ }
57
+ export {};
@@ -0,0 +1,132 @@
1
+ import * as plugins from './plugins.js';
2
+ import { SniHandler } from './classes.pp.snihandler.js';
3
+ /**
4
+ * Manages TLS-related operations including SNI extraction and validation
5
+ */
6
+ export class TlsManager {
7
+ constructor(settings) {
8
+ this.settings = settings;
9
+ }
10
+ /**
11
+ * Check if a data chunk appears to be a TLS handshake
12
+ */
13
+ isTlsHandshake(chunk) {
14
+ return SniHandler.isTlsHandshake(chunk);
15
+ }
16
+ /**
17
+ * Check if a data chunk appears to be a TLS ClientHello
18
+ */
19
+ isClientHello(chunk) {
20
+ return SniHandler.isClientHello(chunk);
21
+ }
22
+ /**
23
+ * Extract Server Name Indication (SNI) from TLS handshake
24
+ */
25
+ extractSNI(chunk, connInfo, previousDomain) {
26
+ // Use the SniHandler to process the TLS packet
27
+ return SniHandler.processTlsPacket(chunk, connInfo, this.settings.enableTlsDebugLogging || false, previousDomain);
28
+ }
29
+ /**
30
+ * Handle session resumption attempts
31
+ */
32
+ handleSessionResumption(chunk, connectionId, hasSNI) {
33
+ // Skip if session tickets are allowed
34
+ if (this.settings.allowSessionTicket !== false) {
35
+ return { shouldBlock: false };
36
+ }
37
+ // Check for session resumption attempt
38
+ const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging || false);
39
+ // If this is a resumption attempt without SNI, block it
40
+ if (resumptionInfo.isResumption && !hasSNI && !resumptionInfo.hasSNI) {
41
+ if (this.settings.enableTlsDebugLogging) {
42
+ console.log(`[${connectionId}] Session resumption detected without SNI and allowSessionTicket=false. ` +
43
+ `Terminating connection to force new TLS handshake.`);
44
+ }
45
+ return {
46
+ shouldBlock: true,
47
+ reason: 'session_ticket_blocked'
48
+ };
49
+ }
50
+ return { shouldBlock: false };
51
+ }
52
+ /**
53
+ * Check for SNI mismatch during renegotiation
54
+ */
55
+ checkRenegotiationSNI(chunk, connInfo, expectedDomain, connectionId) {
56
+ // Only process if this looks like a TLS ClientHello
57
+ if (!this.isClientHello(chunk)) {
58
+ return { hasMismatch: false };
59
+ }
60
+ try {
61
+ // Extract SNI with renegotiation support
62
+ const newSNI = SniHandler.extractSNIWithResumptionSupport(chunk, connInfo, this.settings.enableTlsDebugLogging || false);
63
+ // Skip if no SNI was found
64
+ if (!newSNI)
65
+ return { hasMismatch: false };
66
+ // Check for SNI mismatch
67
+ if (newSNI !== expectedDomain) {
68
+ if (this.settings.enableTlsDebugLogging) {
69
+ console.log(`[${connectionId}] Renegotiation with different SNI: ${expectedDomain} -> ${newSNI}. ` +
70
+ `Terminating connection - SNI domain switching is not allowed.`);
71
+ }
72
+ return { hasMismatch: true, extractedSNI: newSNI };
73
+ }
74
+ else if (this.settings.enableTlsDebugLogging) {
75
+ console.log(`[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`);
76
+ }
77
+ }
78
+ catch (err) {
79
+ console.log(`[${connectionId}] Error processing ClientHello: ${err}. Allowing connection to continue.`);
80
+ }
81
+ return { hasMismatch: false };
82
+ }
83
+ /**
84
+ * Create a renegotiation handler function for a connection
85
+ */
86
+ createRenegotiationHandler(connectionId, lockedDomain, connInfo, onMismatch) {
87
+ return (chunk) => {
88
+ const result = this.checkRenegotiationSNI(chunk, connInfo, lockedDomain, connectionId);
89
+ if (result.hasMismatch) {
90
+ onMismatch(connectionId, 'sni_mismatch');
91
+ }
92
+ };
93
+ }
94
+ /**
95
+ * Analyze TLS connection for browser fingerprinting
96
+ * This helps identify browser vs non-browser connections
97
+ */
98
+ analyzeClientHello(chunk) {
99
+ // Default result
100
+ const result = {
101
+ isBrowserConnection: false,
102
+ isRenewal: false,
103
+ hasSNI: false
104
+ };
105
+ try {
106
+ // Check if it's a ClientHello
107
+ if (!this.isClientHello(chunk)) {
108
+ return result;
109
+ }
110
+ // Check for session resumption
111
+ const resumptionInfo = SniHandler.hasSessionResumption(chunk, this.settings.enableTlsDebugLogging || false);
112
+ // Extract SNI
113
+ const sni = SniHandler.extractSNI(chunk, this.settings.enableTlsDebugLogging || false);
114
+ // Update result
115
+ result.isRenewal = resumptionInfo.isResumption;
116
+ result.hasSNI = !!sni;
117
+ // Browsers typically:
118
+ // 1. Send SNI extension
119
+ // 2. Have a variety of extensions (ALPN, etc.)
120
+ // 3. Use standard cipher suites
121
+ // ...more complex heuristics could be implemented here
122
+ // Simple heuristic: presence of SNI suggests browser
123
+ result.isBrowserConnection = !!sni;
124
+ return result;
125
+ }
126
+ catch (err) {
127
+ console.log(`Error analyzing ClientHello: ${err}`);
128
+ return result;
129
+ }
130
+ }
131
+ }
132
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC50bHNtYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5wcC50bHNtYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBRXhDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQVl4RDs7R0FFRztBQUNILE1BQU0sT0FBTyxVQUFVO0lBQ3JCLFlBQW9CLFFBQTRCO1FBQTVCLGFBQVEsR0FBUixRQUFRLENBQW9CO0lBQUcsQ0FBQztJQUVwRDs7T0FFRztJQUNJLGNBQWMsQ0FBQyxLQUFhO1FBQ2pDLE9BQU8sVUFBVSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxhQUFhLENBQUMsS0FBYTtRQUNoQyxPQUFPLFVBQVUsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksVUFBVSxDQUNmLEtBQWEsRUFDYixRQUF5QixFQUN6QixjQUF1QjtRQUV2QiwrQ0FBK0M7UUFDL0MsT0FBTyxVQUFVLENBQUMsZ0JBQWdCLENBQ2hDLEtBQUssRUFDTCxRQUFRLEVBQ1IsSUFBSSxDQUFDLFFBQVEsQ0FBQyxxQkFBcUIsSUFBSSxLQUFLLEVBQzVDLGNBQWMsQ0FDZixDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksdUJBQXVCLENBQzVCLEtBQWEsRUFDYixZQUFvQixFQUNwQixNQUFlO1FBRWYsc0NBQXNDO1FBQ3RDLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxrQkFBa0IsS0FBSyxLQUFLLEVBQUUsQ0FBQztZQUMvQyxPQUFPLEVBQUUsV0FBVyxFQUFFLEtBQUssRUFBRSxDQUFDO1FBQ2hDLENBQUM7UUFFRCx1Q0FBdUM7UUFDdkMsTUFBTSxjQUFjLEdBQUcsVUFBVSxDQUFDLG9CQUFvQixDQUNwRCxLQUFL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
@@ -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';
package/dist_ts/index.js CHANGED
@@ -1,7 +1,7 @@
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';
7
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyx5QkFBeUIsQ0FBQyJ9
6
+ export * from './classes.pp.snihandler.js';
7
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyw0QkFBNEIsQ0FBQyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "3.41.8",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
6
6
  "main": "dist_ts/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '3.41.8',
6
+ version: '4.1.0',
7
7
  description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
8
8
  }
@@ -0,0 +1,149 @@
1
+ import type { IPortProxySettings } from './classes.pp.interfaces.js';
2
+ import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
3
+
4
+ /**
5
+ * Manages ACME certificate operations
6
+ */
7
+ export class AcmeManager {
8
+ constructor(
9
+ private settings: IPortProxySettings,
10
+ private networkProxyBridge: NetworkProxyBridge
11
+ ) {}
12
+
13
+ /**
14
+ * Get current ACME settings
15
+ */
16
+ public getAcmeSettings(): IPortProxySettings['acme'] {
17
+ return this.settings.acme;
18
+ }
19
+
20
+ /**
21
+ * Check if ACME is enabled
22
+ */
23
+ public isAcmeEnabled(): boolean {
24
+ return !!this.settings.acme?.enabled;
25
+ }
26
+
27
+ /**
28
+ * Update ACME certificate settings
29
+ */
30
+ public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> {
31
+ console.log('Updating ACME certificate settings');
32
+
33
+ // Check if enabled state is changing
34
+ const enabledChanging = this.settings.acme?.enabled !== acmeSettings.enabled;
35
+
36
+ // Update settings
37
+ this.settings.acme = {
38
+ ...this.settings.acme,
39
+ ...acmeSettings,
40
+ };
41
+
42
+ // Get NetworkProxy instance
43
+ const networkProxy = this.networkProxyBridge.getNetworkProxy();
44
+
45
+ if (!networkProxy) {
46
+ console.log('Cannot update ACME settings - NetworkProxy not initialized');
47
+ return;
48
+ }
49
+
50
+ try {
51
+ // If enabled state changed, we need to restart NetworkProxy
52
+ if (enabledChanging) {
53
+ console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`);
54
+
55
+ // Stop the current NetworkProxy
56
+ await this.networkProxyBridge.stop();
57
+
58
+ // Reinitialize with new settings
59
+ await this.networkProxyBridge.initialize();
60
+
61
+ // Start NetworkProxy with new settings
62
+ await this.networkProxyBridge.start();
63
+ } else {
64
+ // Just update the settings in the existing NetworkProxy
65
+ console.log('Updating ACME settings in NetworkProxy without restart');
66
+
67
+ // Update settings in NetworkProxy
68
+ if (networkProxy.options && networkProxy.options.acme) {
69
+ networkProxy.options.acme = { ...this.settings.acme };
70
+
71
+ // For certificate renewals, we might want to trigger checks with the new settings
72
+ if (acmeSettings.renewThresholdDays !== undefined) {
73
+ console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`);
74
+ networkProxy.options.acme.renewThresholdDays = acmeSettings.renewThresholdDays;
75
+ }
76
+
77
+ // Update other settings that might affect certificate operations
78
+ if (acmeSettings.useProduction !== undefined) {
79
+ console.log(`Setting ACME to ${acmeSettings.useProduction ? 'production' : 'staging'} mode`);
80
+ }
81
+
82
+ if (acmeSettings.autoRenew !== undefined) {
83
+ console.log(`Setting auto-renewal to ${acmeSettings.autoRenew ? 'enabled' : 'disabled'}`);
84
+ }
85
+ }
86
+ }
87
+ } catch (err) {
88
+ console.log(`Error updating ACME settings: ${err}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Request a certificate for a specific domain
94
+ */
95
+ public async requestCertificate(domain: string): Promise<boolean> {
96
+ // Validate domain format
97
+ if (!this.isValidDomain(domain)) {
98
+ console.log(`Invalid domain format: ${domain}`);
99
+ return false;
100
+ }
101
+
102
+ // Delegate to NetworkProxyManager
103
+ return this.networkProxyBridge.requestCertificate(domain);
104
+ }
105
+
106
+ /**
107
+ * Basic domain validation
108
+ */
109
+ private isValidDomain(domain: string): boolean {
110
+ // Very basic domain validation
111
+ if (!domain || domain.length === 0) {
112
+ return false;
113
+ }
114
+
115
+ // Check for wildcard domains (they can't get ACME certs)
116
+ if (domain.includes('*')) {
117
+ console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`);
118
+ return false;
119
+ }
120
+
121
+ // Check if domain has at least one dot and no invalid characters
122
+ const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
123
+ if (!validDomainRegex.test(domain)) {
124
+ console.log(`Domain "${domain}" has invalid format`);
125
+ return false;
126
+ }
127
+
128
+ return true;
129
+ }
130
+
131
+ /**
132
+ * Get eligible domains for ACME certificates
133
+ */
134
+ public getEligibleDomains(): string[] {
135
+ // Collect all eligible domains from domain configs
136
+ const domains: string[] = [];
137
+
138
+ for (const config of this.settings.domainConfigs) {
139
+ // Skip domains that can't be used with ACME
140
+ const eligibleDomains = config.domains.filter(domain =>
141
+ !domain.includes('*') && this.isValidDomain(domain)
142
+ );
143
+
144
+ domains.push(...eligibleDomains);
145
+ }
146
+
147
+ return domains;
148
+ }
149
+ }