@push.rocks/smartnetwork 4.9.0 → 4.10.1
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_rust/rustnetwork_linux_amd64 +0 -0
- package/dist_rust/rustnetwork_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/smartnetwork.classes.domainintelligence.d.ts +23 -62
- package/dist_ts/smartnetwork.classes.domainintelligence.js +113 -357
- package/dist_ts/smartnetwork.classes.ipintelligence.d.ts +11 -36
- package/dist_ts/smartnetwork.classes.ipintelligence.js +83 -316
- package/dist_ts/smartnetwork.classes.rustbridge.d.ts +12 -8
- package/dist_ts/smartnetwork.classes.rustbridge.js +70 -15
- package/dist_ts/smartnetwork.classes.smartnetwork.d.ts +10 -3
- package/dist_ts/smartnetwork.classes.smartnetwork.js +132 -50
- package/dist_ts/smartnetwork.plugins.d.ts +2 -1
- package/dist_ts/smartnetwork.plugins.js +3 -2
- package/package.json +8 -7
- package/readme.hints.md +3 -3
- package/readme.md +23 -9
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/smartnetwork.classes.domainintelligence.ts +121 -381
- package/ts/smartnetwork.classes.ipintelligence.ts +88 -345
- package/ts/smartnetwork.classes.rustbridge.ts +72 -14
- package/ts/smartnetwork.classes.smartnetwork.ts +140 -52
- package/ts/smartnetwork.plugins.ts +2 -1
|
@@ -5,7 +5,10 @@ import { IpIntelligence, type IIpIntelligenceResult } from './smartnetwork.class
|
|
|
5
5
|
import { DomainIntelligence, type IDomainIntelligenceResult } from './smartnetwork.classes.domainintelligence.js';
|
|
6
6
|
import { getLogger } from './logging.js';
|
|
7
7
|
import { NetworkError } from './errors.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
RustNetworkBridge,
|
|
10
|
+
RustNetworkBridgeActivationCleanupError,
|
|
11
|
+
} from './smartnetwork.classes.rustbridge.js';
|
|
9
12
|
|
|
10
13
|
/** Type alias for the shared Smartdns client instance */
|
|
11
14
|
type TSmartdnsClient = InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns>;
|
|
@@ -39,6 +42,8 @@ export interface IFindFreePortOptions {
|
|
|
39
42
|
exclude?: number[];
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
type TBridgeLifecycleState = 'idle' | 'active' | 'release-failed';
|
|
46
|
+
|
|
42
47
|
/**
|
|
43
48
|
* SmartNetwork simplifies actions within the network.
|
|
44
49
|
* Uses a Rust binary for system-dependent operations (ping, traceroute, port scanning, gateway detection).
|
|
@@ -58,7 +63,11 @@ export class SmartNetwork {
|
|
|
58
63
|
private options: SmartNetworkOptions;
|
|
59
64
|
private cache: Map<string, { value: any; expiry: number }>;
|
|
60
65
|
private rustBridge: RustNetworkBridge;
|
|
61
|
-
private
|
|
66
|
+
private bridgeLifecycleState: TBridgeLifecycleState = 'idle';
|
|
67
|
+
private bridgeLifecycleQueue: Promise<void> = Promise.resolve();
|
|
68
|
+
private activeDnsOperations = 0;
|
|
69
|
+
private dnsDrainPromise: Promise<void> | null = null;
|
|
70
|
+
private resolveDnsDrain: (() => void) | null = null;
|
|
62
71
|
private ipIntelligence: IpIntelligence | null = null;
|
|
63
72
|
private domainIntelligence: DomainIntelligence | null = null;
|
|
64
73
|
private dnsClient: TSmartdnsClient | null = null;
|
|
@@ -73,11 +82,21 @@ export class SmartNetwork {
|
|
|
73
82
|
* Start the Rust binary bridge. Must be called before using ping, traceroute,
|
|
74
83
|
* port scanning, or gateway operations. Safe to call multiple times.
|
|
75
84
|
*/
|
|
76
|
-
public
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
public start(): Promise<void> {
|
|
86
|
+
return this.enqueueBridgeLifecycle(async () => {
|
|
87
|
+
if (this.bridgeLifecycleState === 'release-failed') {
|
|
88
|
+
throw new Error('SmartNetwork cleanup must succeed before it can start again');
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await this.rustBridge.start(this);
|
|
92
|
+
this.bridgeLifecycleState = 'active';
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof RustNetworkBridgeActivationCleanupError) {
|
|
95
|
+
this.bridgeLifecycleState = 'release-failed';
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
81
100
|
}
|
|
82
101
|
|
|
83
102
|
/**
|
|
@@ -86,35 +105,100 @@ export class SmartNetwork {
|
|
|
86
105
|
* Rust-backed features, otherwise the smartdns Rust backend may keep
|
|
87
106
|
* the event loop alive.
|
|
88
107
|
*/
|
|
89
|
-
public
|
|
90
|
-
|
|
91
|
-
await this.
|
|
92
|
-
|
|
108
|
+
public stop(): Promise<void> {
|
|
109
|
+
return this.enqueueBridgeLifecycle(async () => {
|
|
110
|
+
await this.waitForDnsOperations();
|
|
111
|
+
|
|
112
|
+
let bridgeError: unknown;
|
|
113
|
+
if (this.bridgeLifecycleState !== 'idle') {
|
|
114
|
+
try {
|
|
115
|
+
await this.rustBridge.stop(this);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
bridgeError = error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let dnsError: unknown;
|
|
122
|
+
if (this.dnsClient) {
|
|
123
|
+
try {
|
|
124
|
+
await this.dnsClient.terminate();
|
|
125
|
+
this.dnsClient = null;
|
|
126
|
+
// Intelligence instances hold a stale reference to the terminated
|
|
127
|
+
// client; drop them so the next call rebuilds with a fresh one.
|
|
128
|
+
this.ipIntelligence = null;
|
|
129
|
+
this.domainIntelligence = null;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
dnsError = error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (bridgeError || dnsError) {
|
|
136
|
+
this.bridgeLifecycleState = 'release-failed';
|
|
137
|
+
} else {
|
|
138
|
+
this.bridgeLifecycleState = 'idle';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (bridgeError && dnsError) {
|
|
142
|
+
throw new AggregateError([bridgeError, dnsError], 'SmartNetwork cleanup failed');
|
|
143
|
+
}
|
|
144
|
+
if (bridgeError) throw bridgeError;
|
|
145
|
+
if (dnsError) throw dnsError;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private enqueueBridgeLifecycle(operationArg: () => Promise<void>): Promise<void> {
|
|
150
|
+
const result = this.bridgeLifecycleQueue.then(operationArg);
|
|
151
|
+
this.bridgeLifecycleQueue = result.catch(() => {});
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private async withDnsClient<T>(
|
|
156
|
+
operationArg: (dnsClientArg: TSmartdnsClient) => Promise<T>,
|
|
157
|
+
): Promise<T> {
|
|
158
|
+
let dnsClient: TSmartdnsClient;
|
|
159
|
+
await this.enqueueBridgeLifecycle(async () => {
|
|
160
|
+
if (this.bridgeLifecycleState === 'release-failed') {
|
|
161
|
+
throw new Error('SmartNetwork cleanup must succeed before DNS can be used again');
|
|
162
|
+
}
|
|
163
|
+
dnsClient = this.ensureDnsClient();
|
|
164
|
+
this.activeDnsOperations++;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
return await operationArg(dnsClient!);
|
|
169
|
+
} finally {
|
|
170
|
+
this.activeDnsOperations--;
|
|
171
|
+
if (this.activeDnsOperations === 0 && this.resolveDnsDrain) {
|
|
172
|
+
const resolve = this.resolveDnsDrain;
|
|
173
|
+
this.resolveDnsDrain = null;
|
|
174
|
+
this.dnsDrainPromise = null;
|
|
175
|
+
resolve();
|
|
176
|
+
}
|
|
93
177
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private waitForDnsOperations(): Promise<void> {
|
|
181
|
+
if (this.activeDnsOperations === 0) return Promise.resolve();
|
|
182
|
+
if (!this.dnsDrainPromise) {
|
|
183
|
+
this.dnsDrainPromise = new Promise<void>((resolve) => {
|
|
184
|
+
this.resolveDnsDrain = resolve;
|
|
185
|
+
});
|
|
101
186
|
}
|
|
187
|
+
return this.dnsDrainPromise;
|
|
102
188
|
}
|
|
103
189
|
|
|
104
190
|
/**
|
|
105
191
|
* Ensure the Rust bridge is running before sending commands.
|
|
106
192
|
*/
|
|
107
193
|
private async ensureBridge(): Promise<void> {
|
|
108
|
-
|
|
109
|
-
await this.start();
|
|
110
|
-
}
|
|
194
|
+
await this.start();
|
|
111
195
|
}
|
|
112
196
|
|
|
113
197
|
/**
|
|
114
198
|
* Lazily create the shared Smartdns client. The Rust backend inside
|
|
115
199
|
* Smartdns is only spawned on first query that requires it (NS/MX/SOA
|
|
116
200
|
* with prefer-system strategy, or any query with doh/udp strategy).
|
|
117
|
-
* The client is
|
|
201
|
+
* The client is terminated by stop().
|
|
118
202
|
*/
|
|
119
203
|
private ensureDnsClient(): TSmartdnsClient {
|
|
120
204
|
if (!this.dnsClient) {
|
|
@@ -348,31 +432,31 @@ export class SmartNetwork {
|
|
|
348
432
|
/**
|
|
349
433
|
* Resolve DNS records (A, AAAA, MX) via the shared smartdns client.
|
|
350
434
|
* The client is lifecycle-managed by start()/stop() — MX queries spawn
|
|
351
|
-
* the smartdns Rust bridge,
|
|
435
|
+
* the smartdns Rust bridge, whose termination is confirmed by stop().
|
|
352
436
|
*/
|
|
353
437
|
public async resolveDns(
|
|
354
438
|
host: string,
|
|
355
439
|
): Promise<{ A: string[]; AAAA: string[]; MX: { exchange: string; priority: number }[] }> {
|
|
356
440
|
try {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
};
|
|
441
|
+
return await this.withDnsClient(async (dnsClient) => {
|
|
442
|
+
const [aRecords, aaaaRecords, mxRecords] = await Promise.all([
|
|
443
|
+
dnsClient.getRecordsA(host).catch((): any[] => []),
|
|
444
|
+
dnsClient.getRecordsAAAA(host).catch((): any[] => []),
|
|
445
|
+
dnsClient.getRecords(host, 'MX').catch((): any[] => []),
|
|
446
|
+
]);
|
|
447
|
+
|
|
448
|
+
const A = aRecords.map((record: any) => record.value);
|
|
449
|
+
const AAAA = aaaaRecords.map((record: any) => record.value);
|
|
450
|
+
const MX = mxRecords.map((record: any) => {
|
|
451
|
+
const parts = record.value.split(' ');
|
|
452
|
+
return {
|
|
453
|
+
priority: parseInt(parts[0], 10),
|
|
454
|
+
exchange: parts[1] || '',
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
return { A, AAAA, MX };
|
|
373
459
|
});
|
|
374
|
-
|
|
375
|
-
return { A, AAAA, MX };
|
|
376
460
|
} catch (err: any) {
|
|
377
461
|
throw new NetworkError(err.message, err.code);
|
|
378
462
|
}
|
|
@@ -438,13 +522,15 @@ export class SmartNetwork {
|
|
|
438
522
|
* Pure TS, no Rust needed.
|
|
439
523
|
*/
|
|
440
524
|
public async getIpIntelligence(ip: string): Promise<IIpIntelligenceResult> {
|
|
441
|
-
|
|
442
|
-
this.ipIntelligence
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
525
|
+
const fetcher = () => this.withDnsClient(async (dnsClient) => {
|
|
526
|
+
if (!this.ipIntelligence) {
|
|
527
|
+
this.ipIntelligence = new IpIntelligence({
|
|
528
|
+
dnsClient,
|
|
529
|
+
timeout: this.options.ipIntelligenceTimeout,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
return this.ipIntelligence.getIntelligence(ip);
|
|
533
|
+
});
|
|
448
534
|
if (this.options.cacheTtl && this.options.cacheTtl > 0) {
|
|
449
535
|
return this.getCached(`ipIntelligence:${ip}`, fetcher);
|
|
450
536
|
}
|
|
@@ -457,10 +543,12 @@ export class SmartNetwork {
|
|
|
457
543
|
* Rust needed.
|
|
458
544
|
*/
|
|
459
545
|
public async getDomainIntelligence(domain: string): Promise<IDomainIntelligenceResult> {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
546
|
+
const fetcher = () => this.withDnsClient(async (dnsClient) => {
|
|
547
|
+
if (!this.domainIntelligence) {
|
|
548
|
+
this.domainIntelligence = new DomainIntelligence({ dnsClient });
|
|
549
|
+
}
|
|
550
|
+
return this.domainIntelligence.getIntelligence(domain);
|
|
551
|
+
});
|
|
464
552
|
if (this.options.cacheTtl && this.options.cacheTtl > 0) {
|
|
465
553
|
return this.getCached(`domainIntelligence:${domain}`, fetcher);
|
|
466
554
|
}
|
|
@@ -8,8 +8,9 @@ export { os, https, perfHooks };
|
|
|
8
8
|
// @pushrocks scope
|
|
9
9
|
import * as smartdns from '@push.rocks/smartdns';
|
|
10
10
|
import * as smartrust from '@push.rocks/smartrust';
|
|
11
|
+
import * as smartwhois from '@push.rocks/smartwhois';
|
|
11
12
|
|
|
12
|
-
export { smartdns, smartrust };
|
|
13
|
+
export { smartdns, smartrust, smartwhois };
|
|
13
14
|
|
|
14
15
|
// third party
|
|
15
16
|
import * as maxmind from 'maxmind';
|