@push.rocks/smartproxy 19.5.4 → 19.5.5
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/utils/async-utils.d.ts +81 -0
- package/dist_ts/core/utils/async-utils.js +216 -0
- package/dist_ts/core/utils/binary-heap.d.ts +73 -0
- package/dist_ts/core/utils/binary-heap.js +193 -0
- package/dist_ts/core/utils/enhanced-connection-pool.d.ts +110 -0
- package/dist_ts/core/utils/enhanced-connection-pool.js +320 -0
- package/dist_ts/core/utils/fs-utils.d.ts +144 -0
- package/dist_ts/core/utils/fs-utils.js +252 -0
- package/dist_ts/core/utils/index.d.ts +5 -2
- package/dist_ts/core/utils/index.js +6 -3
- package/dist_ts/core/utils/lifecycle-component.d.ts +59 -0
- package/dist_ts/core/utils/lifecycle-component.js +195 -0
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/proxies/http-proxy/certificate-manager.d.ts +15 -0
- package/dist_ts/proxies/http-proxy/certificate-manager.js +49 -2
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +10 -0
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +53 -43
- package/dist_ts/proxies/smart-proxy/cert-store.js +22 -20
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +37 -7
- package/dist_ts/proxies/smart-proxy/connection-manager.js +257 -180
- package/package.json +2 -2
- package/readme.hints.md +96 -1
- package/readme.plan.md +1135 -221
- package/readme.problems.md +167 -83
- package/ts/core/utils/async-utils.ts +275 -0
- package/ts/core/utils/binary-heap.ts +225 -0
- package/ts/core/utils/enhanced-connection-pool.ts +420 -0
- package/ts/core/utils/fs-utils.ts +270 -0
- package/ts/core/utils/index.ts +5 -2
- package/ts/core/utils/lifecycle-component.ts +231 -0
- package/ts/plugins.ts +2 -1
- package/ts/proxies/http-proxy/certificate-manager.ts +52 -1
- package/ts/proxies/nftables-proxy/nftables-proxy.ts +64 -79
- package/ts/proxies/smart-proxy/cert-store.ts +26 -20
- package/ts/proxies/smart-proxy/connection-manager.ts +291 -189
- package/readme.plan2.md +0 -764
- package/ts/common/eventUtils.ts +0 -34
- package/ts/common/types.ts +0 -91
- package/ts/core/utils/event-system.ts +0 -376
- package/ts/core/utils/event-utils.ts +0 -25
|
@@ -3,6 +3,8 @@ import { promisify } from 'util';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import * as os from 'os';
|
|
6
|
+
import { delay } from '../../core/utils/async-utils.js';
|
|
7
|
+
import { AsyncFileSystem } from '../../core/utils/fs-utils.js';
|
|
6
8
|
import {
|
|
7
9
|
NftBaseError,
|
|
8
10
|
NftValidationError,
|
|
@@ -208,7 +210,7 @@ export class NfTablesProxy {
|
|
|
208
210
|
|
|
209
211
|
// Wait before retry, unless it's the last attempt
|
|
210
212
|
if (i < maxRetries - 1) {
|
|
211
|
-
await
|
|
213
|
+
await delay(retryDelayMs);
|
|
212
214
|
}
|
|
213
215
|
}
|
|
214
216
|
}
|
|
@@ -218,8 +220,13 @@ export class NfTablesProxy {
|
|
|
218
220
|
|
|
219
221
|
/**
|
|
220
222
|
* Execute system command synchronously with multiple attempts
|
|
223
|
+
* @deprecated This method blocks the event loop and should be avoided. Use executeWithRetry instead.
|
|
224
|
+
* WARNING: This method contains a busy wait loop that will block the entire Node.js event loop!
|
|
221
225
|
*/
|
|
222
226
|
private executeWithRetrySync(command: string, maxRetries = 3, retryDelayMs = 1000): string {
|
|
227
|
+
// Log deprecation warning
|
|
228
|
+
console.warn('[DEPRECATION WARNING] executeWithRetrySync blocks the event loop and should not be used. Consider using the async executeWithRetry method instead.');
|
|
229
|
+
|
|
223
230
|
let lastError: Error | undefined;
|
|
224
231
|
|
|
225
232
|
for (let i = 0; i < maxRetries; i++) {
|
|
@@ -231,10 +238,12 @@ export class NfTablesProxy {
|
|
|
231
238
|
|
|
232
239
|
// Wait before retry, unless it's the last attempt
|
|
233
240
|
if (i < maxRetries - 1) {
|
|
234
|
-
//
|
|
241
|
+
// CRITICAL: This busy wait loop blocks the entire event loop!
|
|
242
|
+
// This is a temporary fallback for sync contexts only.
|
|
243
|
+
// TODO: Remove this method entirely and make all callers async
|
|
235
244
|
const waitUntil = Date.now() + retryDelayMs;
|
|
236
245
|
while (Date.now() < waitUntil) {
|
|
237
|
-
//
|
|
246
|
+
// Busy wait - blocks event loop
|
|
238
247
|
}
|
|
239
248
|
}
|
|
240
249
|
}
|
|
@@ -243,6 +252,26 @@ export class NfTablesProxy {
|
|
|
243
252
|
throw new NftExecutionError(`Failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`);
|
|
244
253
|
}
|
|
245
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Execute nftables commands with a temporary file
|
|
257
|
+
* This helper handles the common pattern of writing rules to a temp file,
|
|
258
|
+
* executing nftables with the file, and cleaning up
|
|
259
|
+
*/
|
|
260
|
+
private async executeWithTempFile(rulesetContent: string): Promise<void> {
|
|
261
|
+
await AsyncFileSystem.writeFile(this.tempFilePath, rulesetContent);
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
await this.executeWithRetry(
|
|
265
|
+
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
266
|
+
this.settings.maxRetries,
|
|
267
|
+
this.settings.retryDelayMs
|
|
268
|
+
);
|
|
269
|
+
} finally {
|
|
270
|
+
// Always clean up the temp file
|
|
271
|
+
await AsyncFileSystem.remove(this.tempFilePath);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
246
275
|
/**
|
|
247
276
|
* Checks if nftables is available and the required modules are loaded
|
|
248
277
|
*/
|
|
@@ -545,15 +574,8 @@ export class NfTablesProxy {
|
|
|
545
574
|
|
|
546
575
|
// Only write and apply if we have rules to add
|
|
547
576
|
if (rulesetContent) {
|
|
548
|
-
//
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
// Apply the ruleset
|
|
552
|
-
await this.executeWithRetry(
|
|
553
|
-
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
554
|
-
this.settings.maxRetries,
|
|
555
|
-
this.settings.retryDelayMs
|
|
556
|
-
);
|
|
577
|
+
// Apply the ruleset using the helper
|
|
578
|
+
await this.executeWithTempFile(rulesetContent);
|
|
557
579
|
|
|
558
580
|
this.log('info', `Added source IP filter rules for ${family}`);
|
|
559
581
|
|
|
@@ -566,9 +588,6 @@ export class NfTablesProxy {
|
|
|
566
588
|
await this.verifyRuleApplication(rule);
|
|
567
589
|
}
|
|
568
590
|
}
|
|
569
|
-
|
|
570
|
-
// Remove the temporary file
|
|
571
|
-
fs.unlinkSync(this.tempFilePath);
|
|
572
591
|
}
|
|
573
592
|
|
|
574
593
|
return true;
|
|
@@ -663,13 +682,7 @@ export class NfTablesProxy {
|
|
|
663
682
|
|
|
664
683
|
// Apply the rules if we have any
|
|
665
684
|
if (rulesetContent) {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
await this.executeWithRetry(
|
|
669
|
-
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
670
|
-
this.settings.maxRetries,
|
|
671
|
-
this.settings.retryDelayMs
|
|
672
|
-
);
|
|
685
|
+
await this.executeWithTempFile(rulesetContent);
|
|
673
686
|
|
|
674
687
|
this.log('info', `Added advanced NAT rules for ${family}`);
|
|
675
688
|
|
|
@@ -682,9 +695,6 @@ export class NfTablesProxy {
|
|
|
682
695
|
await this.verifyRuleApplication(rule);
|
|
683
696
|
}
|
|
684
697
|
}
|
|
685
|
-
|
|
686
|
-
// Remove the temporary file
|
|
687
|
-
fs.unlinkSync(this.tempFilePath);
|
|
688
698
|
}
|
|
689
699
|
}
|
|
690
700
|
|
|
@@ -816,15 +826,8 @@ export class NfTablesProxy {
|
|
|
816
826
|
|
|
817
827
|
// Apply the ruleset if we have any rules
|
|
818
828
|
if (rulesetContent) {
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
// Apply the ruleset
|
|
823
|
-
await this.executeWithRetry(
|
|
824
|
-
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
825
|
-
this.settings.maxRetries,
|
|
826
|
-
this.settings.retryDelayMs
|
|
827
|
-
);
|
|
829
|
+
// Apply the ruleset using the helper
|
|
830
|
+
await this.executeWithTempFile(rulesetContent);
|
|
828
831
|
|
|
829
832
|
this.log('info', `Added port forwarding rules for ${family}`);
|
|
830
833
|
|
|
@@ -837,9 +840,6 @@ export class NfTablesProxy {
|
|
|
837
840
|
await this.verifyRuleApplication(rule);
|
|
838
841
|
}
|
|
839
842
|
}
|
|
840
|
-
|
|
841
|
-
// Remove temporary file
|
|
842
|
-
fs.unlinkSync(this.tempFilePath);
|
|
843
843
|
}
|
|
844
844
|
|
|
845
845
|
return true;
|
|
@@ -931,15 +931,7 @@ export class NfTablesProxy {
|
|
|
931
931
|
|
|
932
932
|
// Apply the ruleset if we have any rules
|
|
933
933
|
if (rulesetContent) {
|
|
934
|
-
|
|
935
|
-
fs.writeFileSync(this.tempFilePath, rulesetContent);
|
|
936
|
-
|
|
937
|
-
// Apply the ruleset
|
|
938
|
-
await this.executeWithRetry(
|
|
939
|
-
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
940
|
-
this.settings.maxRetries,
|
|
941
|
-
this.settings.retryDelayMs
|
|
942
|
-
);
|
|
934
|
+
await this.executeWithTempFile(rulesetContent);
|
|
943
935
|
|
|
944
936
|
this.log('info', `Added port forwarding rules for ${family}`);
|
|
945
937
|
|
|
@@ -952,9 +944,6 @@ export class NfTablesProxy {
|
|
|
952
944
|
await this.verifyRuleApplication(rule);
|
|
953
945
|
}
|
|
954
946
|
}
|
|
955
|
-
|
|
956
|
-
// Remove temporary file
|
|
957
|
-
fs.unlinkSync(this.tempFilePath);
|
|
958
947
|
}
|
|
959
948
|
|
|
960
949
|
return true;
|
|
@@ -1027,15 +1016,8 @@ export class NfTablesProxy {
|
|
|
1027
1016
|
|
|
1028
1017
|
// Apply the ruleset if we have any rules
|
|
1029
1018
|
if (rulesetContent) {
|
|
1030
|
-
//
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
// Apply the ruleset
|
|
1034
|
-
await this.executeWithRetry(
|
|
1035
|
-
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
1036
|
-
this.settings.maxRetries,
|
|
1037
|
-
this.settings.retryDelayMs
|
|
1038
|
-
);
|
|
1019
|
+
// Apply the ruleset using the helper
|
|
1020
|
+
await this.executeWithTempFile(rulesetContent);
|
|
1039
1021
|
|
|
1040
1022
|
this.log('info', `Added QoS rules for ${family}`);
|
|
1041
1023
|
|
|
@@ -1048,9 +1030,6 @@ export class NfTablesProxy {
|
|
|
1048
1030
|
await this.verifyRuleApplication(rule);
|
|
1049
1031
|
}
|
|
1050
1032
|
}
|
|
1051
|
-
|
|
1052
|
-
// Remove temporary file
|
|
1053
|
-
fs.unlinkSync(this.tempFilePath);
|
|
1054
1033
|
}
|
|
1055
1034
|
|
|
1056
1035
|
return true;
|
|
@@ -1615,25 +1594,27 @@ export class NfTablesProxy {
|
|
|
1615
1594
|
// Apply the ruleset if we have any rules to delete
|
|
1616
1595
|
if (rulesetContent) {
|
|
1617
1596
|
// Write to temporary file
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
// Apply the ruleset
|
|
1621
|
-
await this.executeWithRetry(
|
|
1622
|
-
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
1623
|
-
this.settings.maxRetries,
|
|
1624
|
-
this.settings.retryDelayMs
|
|
1625
|
-
);
|
|
1626
|
-
|
|
1627
|
-
this.log('info', 'Removed all added rules');
|
|
1628
|
-
|
|
1629
|
-
// Mark all rules as removed
|
|
1630
|
-
this.rules.forEach(rule => {
|
|
1631
|
-
rule.added = false;
|
|
1632
|
-
rule.verified = false;
|
|
1633
|
-
});
|
|
1597
|
+
await AsyncFileSystem.writeFile(this.tempFilePath, rulesetContent);
|
|
1634
1598
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1599
|
+
try {
|
|
1600
|
+
// Apply the ruleset
|
|
1601
|
+
await this.executeWithRetry(
|
|
1602
|
+
`${NfTablesProxy.NFT_CMD} -f ${this.tempFilePath}`,
|
|
1603
|
+
this.settings.maxRetries,
|
|
1604
|
+
this.settings.retryDelayMs
|
|
1605
|
+
);
|
|
1606
|
+
|
|
1607
|
+
this.log('info', 'Removed all added rules');
|
|
1608
|
+
|
|
1609
|
+
// Mark all rules as removed
|
|
1610
|
+
this.rules.forEach(rule => {
|
|
1611
|
+
rule.added = false;
|
|
1612
|
+
rule.verified = false;
|
|
1613
|
+
});
|
|
1614
|
+
} finally {
|
|
1615
|
+
// Remove temporary file
|
|
1616
|
+
await AsyncFileSystem.remove(this.tempFilePath);
|
|
1617
|
+
}
|
|
1637
1618
|
}
|
|
1638
1619
|
|
|
1639
1620
|
// Clean up IP sets if we created any
|
|
@@ -1862,8 +1843,12 @@ export class NfTablesProxy {
|
|
|
1862
1843
|
|
|
1863
1844
|
/**
|
|
1864
1845
|
* Synchronous version of cleanSlate
|
|
1846
|
+
* @deprecated This method blocks the event loop and should be avoided. Use cleanSlate() instead.
|
|
1847
|
+
* WARNING: This method uses execSync which blocks the entire Node.js event loop!
|
|
1865
1848
|
*/
|
|
1866
1849
|
public static cleanSlateSync(): void {
|
|
1850
|
+
console.warn('[DEPRECATION WARNING] cleanSlateSync blocks the event loop and should not be used. Consider using the async cleanSlate() method instead.');
|
|
1851
|
+
|
|
1867
1852
|
try {
|
|
1868
1853
|
// Check for rules with our comment pattern
|
|
1869
1854
|
const stdout = execSync(`${NfTablesProxy.NFT_CMD} list ruleset`).toString();
|
|
@@ -1,36 +1,34 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
|
+
import { AsyncFileSystem } from '../../core/utils/fs-utils.js';
|
|
2
3
|
import type { ICertificateData } from './certificate-manager.js';
|
|
3
4
|
|
|
4
5
|
export class CertStore {
|
|
5
6
|
constructor(private certDir: string) {}
|
|
6
7
|
|
|
7
8
|
public async initialize(): Promise<void> {
|
|
8
|
-
await
|
|
9
|
+
await AsyncFileSystem.ensureDir(this.certDir);
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
public async getCertificate(routeName: string): Promise<ICertificateData | null> {
|
|
12
13
|
const certPath = this.getCertPath(routeName);
|
|
13
14
|
const metaPath = `${certPath}/meta.json`;
|
|
14
15
|
|
|
15
|
-
if (!await
|
|
16
|
+
if (!await AsyncFileSystem.exists(metaPath)) {
|
|
16
17
|
return null;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
try {
|
|
20
|
-
const
|
|
21
|
-
const meta = JSON.parse(metaFile.contents.toString());
|
|
21
|
+
const meta = await AsyncFileSystem.readJSON(metaPath);
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const key = keyFile.contents.toString();
|
|
23
|
+
const [cert, key] = await Promise.all([
|
|
24
|
+
AsyncFileSystem.readFile(`${certPath}/cert.pem`),
|
|
25
|
+
AsyncFileSystem.readFile(`${certPath}/key.pem`)
|
|
26
|
+
]);
|
|
28
27
|
|
|
29
28
|
let ca: string | undefined;
|
|
30
29
|
const caPath = `${certPath}/ca.pem`;
|
|
31
|
-
if (await
|
|
32
|
-
|
|
33
|
-
ca = caFile.contents.toString();
|
|
30
|
+
if (await AsyncFileSystem.exists(caPath)) {
|
|
31
|
+
ca = await AsyncFileSystem.readFile(caPath);
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
return {
|
|
@@ -51,14 +49,18 @@ export class CertStore {
|
|
|
51
49
|
certData: ICertificateData
|
|
52
50
|
): Promise<void> {
|
|
53
51
|
const certPath = this.getCertPath(routeName);
|
|
54
|
-
await
|
|
52
|
+
await AsyncFileSystem.ensureDir(certPath);
|
|
55
53
|
|
|
56
|
-
// Save certificate files
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
// Save certificate files in parallel
|
|
55
|
+
const savePromises = [
|
|
56
|
+
AsyncFileSystem.writeFile(`${certPath}/cert.pem`, certData.cert),
|
|
57
|
+
AsyncFileSystem.writeFile(`${certPath}/key.pem`, certData.key)
|
|
58
|
+
];
|
|
59
59
|
|
|
60
60
|
if (certData.ca) {
|
|
61
|
-
|
|
61
|
+
savePromises.push(
|
|
62
|
+
AsyncFileSystem.writeFile(`${certPath}/ca.pem`, certData.ca)
|
|
63
|
+
);
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
// Save metadata
|
|
@@ -68,13 +70,17 @@ export class CertStore {
|
|
|
68
70
|
savedAt: new Date().toISOString()
|
|
69
71
|
};
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
savePromises.push(
|
|
74
|
+
AsyncFileSystem.writeJSON(`${certPath}/meta.json`, meta)
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
await Promise.all(savePromises);
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
public async deleteCertificate(routeName: string): Promise<void> {
|
|
75
81
|
const certPath = this.getCertPath(routeName);
|
|
76
|
-
if (await
|
|
77
|
-
await
|
|
82
|
+
if (await AsyncFileSystem.isDirectory(certPath)) {
|
|
83
|
+
await AsyncFileSystem.removeDir(certPath);
|
|
78
84
|
}
|
|
79
85
|
}
|
|
80
86
|
|