@push.rocks/smartnetwork 4.10.0 → 5.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.
- package/.smartconfig.json +19 -8
- package/dist_rust/rustnetwork_linux_amd64_musl +0 -0
- package/dist_rust/rustnetwork_linux_amd64_musl.tsrust-build.json +14 -0
- package/dist_rust/rustnetwork_linux_arm64_musl +0 -0
- package/dist_rust/rustnetwork_linux_arm64_musl.tsrust-build.json +14 -0
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/smartnetwork.classes.domainintelligence.d.ts +10 -4
- package/dist_ts/smartnetwork.classes.domainintelligence.js +69 -39
- package/dist_ts/smartnetwork.classes.ipintelligence.d.ts +8 -2
- package/dist_ts/smartnetwork.classes.ipintelligence.js +67 -37
- package/dist_ts/smartnetwork.classes.rustbridge.d.ts +12 -8
- package/dist_ts/smartnetwork.classes.rustbridge.js +82 -32
- package/dist_ts/smartnetwork.classes.smartnetwork.d.ts +46 -8
- package/dist_ts/smartnetwork.classes.smartnetwork.js +165 -84
- package/native-notices/crates/aho-corasick-1.1.4/copying +3 -0
- package/native-notices/crates/aho-corasick-1.1.4/license-mit +21 -0
- package/native-notices/crates/aho-corasick-1.1.4/unlicense +24 -0
- package/native-notices/crates/anstream-1.0.0/license-apache +202 -0
- package/native-notices/crates/anstream-1.0.0/license-mit +19 -0
- package/native-notices/crates/anstyle-1.0.14/license-apache +202 -0
- package/native-notices/crates/anstyle-1.0.14/license-mit +19 -0
- package/native-notices/crates/anstyle-parse-1.0.0/license-apache +202 -0
- package/native-notices/crates/anstyle-parse-1.0.0/license-mit +19 -0
- package/native-notices/crates/anstyle-query-1.1.5/license-apache +202 -0
- package/native-notices/crates/anstyle-query-1.1.5/license-mit +19 -0
- package/native-notices/crates/bytes-1.11.1/license +25 -0
- package/native-notices/crates/cfg-if-1.0.4/license-apache +201 -0
- package/native-notices/crates/cfg-if-1.0.4/license-mit +25 -0
- package/native-notices/crates/clap-4.6.1/license-apache +202 -0
- package/native-notices/crates/clap-4.6.1/license-mit +19 -0
- package/native-notices/crates/clap_builder-4.6.0/license-apache +202 -0
- package/native-notices/crates/clap_builder-4.6.0/license-mit +19 -0
- package/native-notices/crates/clap_derive-4.6.1/license-apache +202 -0
- package/native-notices/crates/clap_derive-4.6.1/license-mit +19 -0
- package/native-notices/crates/clap_lex-1.1.0/license-apache +202 -0
- package/native-notices/crates/clap_lex-1.1.0/license-mit +19 -0
- package/native-notices/crates/colorchoice-1.0.5/license-apache +202 -0
- package/native-notices/crates/colorchoice-1.0.5/license-mit +19 -0
- package/native-notices/crates/errno-0.3.14/license-apache +201 -0
- package/native-notices/crates/errno-0.3.14/license-mit +25 -0
- package/native-notices/crates/getrandom-0.3.4/license-apache +201 -0
- package/native-notices/crates/getrandom-0.3.4/license-mit +26 -0
- package/native-notices/crates/glob-0.3.3/license-apache +201 -0
- package/native-notices/crates/glob-0.3.3/license-mit +25 -0
- package/native-notices/crates/heck-0.5.0/license-apache +201 -0
- package/native-notices/crates/heck-0.5.0/license-mit +25 -0
- package/native-notices/crates/hex-0.4.3/license-apache +202 -0
- package/native-notices/crates/hex-0.4.3/license-mit +20 -0
- package/native-notices/crates/is_terminal_polyfill-1.70.2/license-apache +202 -0
- package/native-notices/crates/is_terminal_polyfill-1.70.2/license-mit +19 -0
- package/native-notices/crates/itoa-1.0.18/license-apache +176 -0
- package/native-notices/crates/itoa-1.0.18/license-mit +23 -0
- package/native-notices/crates/lazy_static-1.5.0/license-apache +201 -0
- package/native-notices/crates/lazy_static-1.5.0/license-mit +25 -0
- package/native-notices/crates/libc-0.2.186/license-apache +176 -0
- package/native-notices/crates/libc-0.2.186/license-mit +25 -0
- package/native-notices/crates/lock_api-0.4.14/license-apache +201 -0
- package/native-notices/crates/lock_api-0.4.14/license-mit +25 -0
- package/native-notices/crates/log-0.4.29/license-apache +201 -0
- package/native-notices/crates/log-0.4.29/license-mit +25 -0
- package/native-notices/crates/matchers-0.2.0/license +19 -0
- package/native-notices/crates/memchr-2.8.0/copying +3 -0
- package/native-notices/crates/memchr-2.8.0/license-mit +21 -0
- package/native-notices/crates/memchr-2.8.0/unlicense +24 -0
- package/native-notices/crates/mio-1.2.0/license +19 -0
- package/native-notices/crates/no-std-net-0.6.0/license +21 -0
- package/native-notices/crates/nu-ansi-term-0.50.3/license +22 -0
- package/native-notices/crates/once_cell-1.21.4/license-apache +201 -0
- package/native-notices/crates/once_cell-1.21.4/license-mit +23 -0
- package/native-notices/crates/parking_lot-0.12.5/license-apache +201 -0
- package/native-notices/crates/parking_lot-0.12.5/license-mit +25 -0
- package/native-notices/crates/parking_lot_core-0.9.12/license-apache +201 -0
- package/native-notices/crates/parking_lot_core-0.9.12/license-mit +25 -0
- package/native-notices/crates/pin-project-lite-0.2.17/license-apache +177 -0
- package/native-notices/crates/pin-project-lite-0.2.17/license-mit +23 -0
- package/native-notices/crates/pnet_base-0.34.0/license-apache +201 -0
- package/native-notices/crates/pnet_base-0.34.0/license-mit +25 -0
- package/native-notices/crates/pnet_macros-0.34.0/license-apache +201 -0
- package/native-notices/crates/pnet_macros-0.34.0/license-mit +25 -0
- package/native-notices/crates/pnet_macros_support-0.34.0/license-apache +201 -0
- package/native-notices/crates/pnet_macros_support-0.34.0/license-mit +25 -0
- package/native-notices/crates/pnet_packet-0.34.0/license-apache +201 -0
- package/native-notices/crates/pnet_packet-0.34.0/license-mit +25 -0
- package/native-notices/crates/ppv-lite86-0.2.21/license-apache +201 -0
- package/native-notices/crates/ppv-lite86-0.2.21/license-mit +25 -0
- package/native-notices/crates/proc-macro2-1.0.106/license-apache +176 -0
- package/native-notices/crates/proc-macro2-1.0.106/license-mit +23 -0
- package/native-notices/crates/quote-1.0.45/license-apache +176 -0
- package/native-notices/crates/quote-1.0.45/license-mit +23 -0
- package/native-notices/crates/rand-0.9.4/copyright +12 -0
- package/native-notices/crates/rand-0.9.4/license-apache +176 -0
- package/native-notices/crates/rand-0.9.4/license-mit +26 -0
- package/native-notices/crates/rand_chacha-0.9.0/copyright +12 -0
- package/native-notices/crates/rand_chacha-0.9.0/license-apache +176 -0
- package/native-notices/crates/rand_chacha-0.9.0/license-mit +26 -0
- package/native-notices/crates/rand_core-0.9.5/copyright +12 -0
- package/native-notices/crates/rand_core-0.9.5/license-apache +187 -0
- package/native-notices/crates/rand_core-0.9.5/license-mit +26 -0
- package/native-notices/crates/regex-1.12.3/license-apache +201 -0
- package/native-notices/crates/regex-1.12.3/license-mit +25 -0
- package/native-notices/crates/regex-automata-0.4.14/license-apache +201 -0
- package/native-notices/crates/regex-automata-0.4.14/license-mit +25 -0
- package/native-notices/crates/regex-syntax-0.8.10/license-apache +201 -0
- package/native-notices/crates/regex-syntax-0.8.10/license-mit +25 -0
- package/native-notices/crates/scopeguard-1.2.0/license-apache +201 -0
- package/native-notices/crates/scopeguard-1.2.0/license-mit +25 -0
- package/native-notices/crates/serde-1.0.228/license-apache +176 -0
- package/native-notices/crates/serde-1.0.228/license-mit +23 -0
- package/native-notices/crates/serde_core-1.0.228/license-apache +176 -0
- package/native-notices/crates/serde_core-1.0.228/license-mit +23 -0
- package/native-notices/crates/serde_derive-1.0.228/license-apache +176 -0
- package/native-notices/crates/serde_derive-1.0.228/license-mit +23 -0
- package/native-notices/crates/serde_json-1.0.149/license-apache +176 -0
- package/native-notices/crates/serde_json-1.0.149/license-mit +23 -0
- package/native-notices/crates/sharded-slab-0.1.7/license +19 -0
- package/native-notices/crates/signal-hook-registry-1.4.8/license-apache +201 -0
- package/native-notices/crates/signal-hook-registry-1.4.8/license-mit +25 -0
- package/native-notices/crates/smallvec-1.15.1/license-apache +201 -0
- package/native-notices/crates/smallvec-1.15.1/license-mit +25 -0
- package/native-notices/crates/socket2-0.5.10/license-apache +201 -0
- package/native-notices/crates/socket2-0.5.10/license-mit +25 -0
- package/native-notices/crates/socket2-0.6.3/license-apache +201 -0
- package/native-notices/crates/socket2-0.6.3/license-mit +25 -0
- package/native-notices/crates/strsim-0.11.1/license +23 -0
- package/native-notices/crates/surge-ping-0.8.4/license +21 -0
- package/native-notices/crates/syn-2.0.117/license-apache +176 -0
- package/native-notices/crates/syn-2.0.117/license-mit +23 -0
- package/native-notices/crates/thiserror-1.0.69/license-apache +176 -0
- package/native-notices/crates/thiserror-1.0.69/license-mit +23 -0
- package/native-notices/crates/thiserror-impl-1.0.69/license-apache +176 -0
- package/native-notices/crates/thiserror-impl-1.0.69/license-mit +23 -0
- package/native-notices/crates/thread_local-1.1.9/license-apache +201 -0
- package/native-notices/crates/thread_local-1.1.9/license-mit +25 -0
- package/native-notices/crates/tokio-1.52.3/license +21 -0
- package/native-notices/crates/tokio-macros-2.7.0/license +22 -0
- package/native-notices/crates/tracing-0.1.44/license +25 -0
- package/native-notices/crates/tracing-attributes-0.1.31/license +25 -0
- package/native-notices/crates/tracing-core-0.1.36/license +25 -0
- package/native-notices/crates/tracing-log-0.2.0/license +25 -0
- package/native-notices/crates/tracing-subscriber-0.3.23/license +25 -0
- package/native-notices/crates/unicode-ident-1.0.24/license-apache +176 -0
- package/native-notices/crates/unicode-ident-1.0.24/license-mit +23 -0
- package/native-notices/crates/unicode-ident-1.0.24/license-unicode +39 -0
- package/native-notices/crates/utf8parse-0.2.2/license-apache +176 -0
- package/native-notices/crates/utf8parse-0.2.2/license-mit +25 -0
- package/native-notices/crates/zerocopy-0.8.48/license-apache +202 -0
- package/native-notices/crates/zerocopy-0.8.48/license-bsd +24 -0
- package/native-notices/crates/zerocopy-0.8.48/license-mit +26 -0
- package/native-notices/crates/zmij-1.0.21/license-mit +23 -0
- package/native-notices/manifest.json +1890 -0
- package/native-notices/readme.md +42 -0
- package/native-notices/runtime/gcc/gpl-3.0.txt +674 -0
- package/native-notices/runtime/gcc/runtime-library-exception-3.1.txt +73 -0
- package/native-notices/runtime/musl-1.2.5/copyright +193 -0
- package/native-notices/rust/compiler-builtins-license.txt +275 -0
- package/native-notices/rust/copyright-library.html +8266 -0
- package/native-notices/rust/crates/addr2line-0.25.1/license-apache +201 -0
- package/native-notices/rust/crates/addr2line-0.25.1/license-mit +25 -0
- package/native-notices/rust/crates/adler2-2.0.1/license-0bsd +12 -0
- package/native-notices/rust/crates/adler2-2.0.1/license-apache +201 -0
- package/native-notices/rust/crates/adler2-2.0.1/license-mit +23 -0
- package/native-notices/rust/crates/memchr-2.7.6/copying +3 -0
- package/native-notices/rust/crates/memchr-2.7.6/license-mit +21 -0
- package/native-notices/rust/crates/memchr-2.7.6/unlicense +24 -0
- package/native-notices/rust/crates/miniz_oxide-0.8.9/license +25 -0
- package/native-notices/rust/crates/miniz_oxide-0.8.9/license-apache.md +177 -0
- package/native-notices/rust/crates/miniz_oxide-0.8.9/license-mit.md +24 -0
- package/native-notices/rust/crates/miniz_oxide-0.8.9/license-zlib.md +14 -0
- package/native-notices/rust/crates/object-0.37.3/license-apache +201 -0
- package/native-notices/rust/crates/object-0.37.3/license-mit +25 -0
- package/native-notices/rust/licenses/apache-2.0.txt +73 -0
- package/native-notices/rust/licenses/bsd-2-clause.txt +9 -0
- package/native-notices/rust/licenses/cc-by-sa-4.0.txt +427 -0
- package/native-notices/rust/licenses/gcc-exception-3.1.txt +30 -0
- package/native-notices/rust/licenses/gpl-2.0-only.txt +133 -0
- package/native-notices/rust/licenses/gpl-3.0-or-later.txt +202 -0
- package/native-notices/rust/licenses/isc.txt +7 -0
- package/native-notices/rust/licenses/llvm-exception.txt +15 -0
- package/native-notices/rust/licenses/mit.txt +9 -0
- package/native-notices/rust/licenses/ncsa.txt +29 -0
- package/native-notices/rust/licenses/ofl-1.1.txt +43 -0
- package/native-notices/rust/licenses/unicode-3.0.txt +39 -0
- package/package.json +18 -9
- package/readme.md +56 -13
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/index.ts +1 -1
- package/ts/smartnetwork.classes.domainintelligence.ts +73 -40
- package/ts/smartnetwork.classes.ipintelligence.ts +71 -37
- package/ts/smartnetwork.classes.rustbridge.ts +83 -34
- package/ts/smartnetwork.classes.smartnetwork.ts +208 -89
- package/dist_rust/rustnetwork_linux_amd64 +0 -0
- package/dist_rust/rustnetwork_linux_arm64 +0 -0
- package/readme.hints.md +0 -106
- package/ts/tsconfig.json +0 -5
- /package/{license → license.md} +0 -0
|
@@ -63,9 +63,9 @@ export interface IDomainIntelligenceOptions {
|
|
|
63
63
|
timeout?: number;
|
|
64
64
|
/**
|
|
65
65
|
* Optional injected smartdns client. When provided, DomainIntelligence
|
|
66
|
-
* will not create or
|
|
66
|
+
* will not create or terminate its own client (the owner — typically
|
|
67
67
|
* SmartNetwork — manages lifecycle). When omitted, a short-lived client
|
|
68
|
-
* is created per DNS-layer query and
|
|
68
|
+
* is created per DNS-layer query and terminated in finally.
|
|
69
69
|
*/
|
|
70
70
|
dnsClient?: TSmartdnsClient;
|
|
71
71
|
}
|
|
@@ -114,6 +114,8 @@ export class DomainIntelligence {
|
|
|
114
114
|
|
|
115
115
|
// Optional injected smartdns client (shared by SmartNetwork)
|
|
116
116
|
private readonly sharedDnsClient: TSmartdnsClient | null;
|
|
117
|
+
private ownedDnsClient: TSmartdnsClient | null = null;
|
|
118
|
+
private dnsLifecycleQueue: Promise<void> = Promise.resolve();
|
|
117
119
|
|
|
118
120
|
constructor(options?: IDomainIntelligenceOptions) {
|
|
119
121
|
this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
@@ -169,9 +171,19 @@ export class DomainIntelligence {
|
|
|
169
171
|
}
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
if (dnsSettled.status === 'rejected') {
|
|
175
|
+
throw dnsSettled.reason;
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
return result;
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
/** Retry and confirm cleanup of a standalone DNS client, if one is retained. */
|
|
182
|
+
public stop(): Promise<void> {
|
|
183
|
+
if (this.sharedDnsClient) return Promise.resolve();
|
|
184
|
+
return this.enqueueDnsLifecycle(() => this.terminateOwnedDnsClient());
|
|
185
|
+
}
|
|
186
|
+
|
|
175
187
|
// ─── RDAP Layer (delegated to @push.rocks/smartwhois) ───────────────
|
|
176
188
|
|
|
177
189
|
/**
|
|
@@ -252,8 +264,8 @@ export class DomainIntelligence {
|
|
|
252
264
|
* field either populated or null.
|
|
253
265
|
*
|
|
254
266
|
* If a shared dnsClient was injected via constructor options, it is
|
|
255
|
-
* reused and NOT
|
|
256
|
-
* a short-lived client is created and
|
|
267
|
+
* reused and NOT terminated (ownership stays with the injector). Otherwise
|
|
268
|
+
* a short-lived client is created and terminated in finally.
|
|
257
269
|
*/
|
|
258
270
|
private async queryDnsLayer(domain: string): Promise<{
|
|
259
271
|
nameservers: string[] | null;
|
|
@@ -263,45 +275,66 @@ export class DomainIntelligence {
|
|
|
263
275
|
txtRecords: string[] | null;
|
|
264
276
|
soaRecord: string | null;
|
|
265
277
|
} | null> {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
278
|
+
const query = async (dnsClientArg: TSmartdnsClient) => {
|
|
279
|
+
try {
|
|
280
|
+
const [nsRes, aRes, aaaaRes, mxRes, txtRes, soaRes] = await Promise.allSettled([
|
|
281
|
+
dnsClientArg.getNameServers(domain),
|
|
282
|
+
dnsClientArg.getRecordsA(domain),
|
|
283
|
+
dnsClientArg.getRecordsAAAA(domain),
|
|
284
|
+
dnsClientArg.getRecords(domain, 'MX'),
|
|
285
|
+
dnsClientArg.getRecordsTxt(domain),
|
|
286
|
+
// 'SOA' is in smartdns's runtime dnsTypeMap but missing from the
|
|
287
|
+
// TDnsRecordType union in @tsclass/tsclass — cast to bypass the
|
|
288
|
+
// stale type definition.
|
|
289
|
+
dnsClientArg.getRecords(domain, 'SOA' as any),
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
nameservers: this.parseNsResult(nsRes),
|
|
294
|
+
resolvedIpv4: this.dnsValuesOrNull(aRes),
|
|
295
|
+
resolvedIpv6: this.dnsValuesOrNull(aaaaRes),
|
|
296
|
+
mxRecords: this.parseMxRecords(mxRes),
|
|
297
|
+
txtRecords: this.dnsValuesOrNull(txtRes),
|
|
298
|
+
soaRecord: this.dnsFirstValueOrNull(soaRes),
|
|
299
|
+
};
|
|
300
|
+
} catch (err: any) {
|
|
301
|
+
this.logger.debug?.(`DNS layer failed for ${domain}: ${err.message}`);
|
|
302
|
+
return null;
|
|
275
303
|
}
|
|
304
|
+
};
|
|
276
305
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
mxRecords: this.parseMxRecords(mxRes),
|
|
294
|
-
txtRecords: this.dnsValuesOrNull(txtRes),
|
|
295
|
-
soaRecord: this.dnsFirstValueOrNull(soaRes),
|
|
296
|
-
};
|
|
297
|
-
} catch (err: any) {
|
|
298
|
-
this.logger.debug?.(`DNS layer failed for ${domain}: ${err.message}`);
|
|
299
|
-
return null;
|
|
300
|
-
} finally {
|
|
301
|
-
// Only destroy clients we created ourselves; leave injected ones alone.
|
|
302
|
-
if (!external && dnsClient) {
|
|
303
|
-
dnsClient.destroy();
|
|
306
|
+
if (this.sharedDnsClient) {
|
|
307
|
+
return query(this.sharedDnsClient);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return this.enqueueDnsLifecycle(async () => {
|
|
311
|
+
await this.terminateOwnedDnsClient();
|
|
312
|
+
const dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
|
313
|
+
strategy: 'prefer-system',
|
|
314
|
+
allowDohFallback: true,
|
|
315
|
+
timeoutMs: this.timeout,
|
|
316
|
+
});
|
|
317
|
+
this.ownedDnsClient = dnsClient;
|
|
318
|
+
try {
|
|
319
|
+
return await query(dnsClient);
|
|
320
|
+
} finally {
|
|
321
|
+
await this.terminateOwnedDnsClient();
|
|
304
322
|
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private enqueueDnsLifecycle<T>(operationArg: () => Promise<T>): Promise<T> {
|
|
327
|
+
const result = this.dnsLifecycleQueue.then(operationArg);
|
|
328
|
+
this.dnsLifecycleQueue = result.then(() => {}, () => {});
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private async terminateOwnedDnsClient(): Promise<void> {
|
|
333
|
+
const dnsClient = this.ownedDnsClient;
|
|
334
|
+
if (!dnsClient) return;
|
|
335
|
+
await dnsClient.terminate();
|
|
336
|
+
if (this.ownedDnsClient === dnsClient) {
|
|
337
|
+
this.ownedDnsClient = null;
|
|
305
338
|
}
|
|
306
339
|
}
|
|
307
340
|
|
|
@@ -53,9 +53,9 @@ export interface IIpIntelligenceOptions {
|
|
|
53
53
|
timeout?: number;
|
|
54
54
|
/**
|
|
55
55
|
* Optional injected smartdns client. When provided, IpIntelligence will
|
|
56
|
-
* not create or
|
|
56
|
+
* not create or terminate its own client (the owner — typically SmartNetwork —
|
|
57
57
|
* manages lifecycle). When omitted, a short-lived client is created per
|
|
58
|
-
* Team Cymru lookup and
|
|
58
|
+
* Team Cymru lookup and terminated in finally.
|
|
59
59
|
*/
|
|
60
60
|
dnsClient?: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns>;
|
|
61
61
|
}
|
|
@@ -89,6 +89,8 @@ export class IpIntelligence {
|
|
|
89
89
|
|
|
90
90
|
// Optional injected smartdns client (shared by SmartNetwork)
|
|
91
91
|
private readonly sharedDnsClient: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns> | null;
|
|
92
|
+
private ownedDnsClient: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns> | null = null;
|
|
93
|
+
private dnsLifecycleQueue: Promise<void> = Promise.resolve();
|
|
92
94
|
|
|
93
95
|
constructor(options?: IIpIntelligenceOptions) {
|
|
94
96
|
this.dbMaxAge = options?.dbMaxAge ?? DEFAULT_DB_MAX_AGE;
|
|
@@ -130,6 +132,10 @@ export class IpIntelligence {
|
|
|
130
132
|
this.queryMaxMind(ip),
|
|
131
133
|
]);
|
|
132
134
|
|
|
135
|
+
if (cymruResult.status === 'rejected') {
|
|
136
|
+
throw cymruResult.reason;
|
|
137
|
+
}
|
|
138
|
+
|
|
133
139
|
// Merge RDAP results
|
|
134
140
|
if (rdapResult.status === 'fulfilled' && rdapResult.value) {
|
|
135
141
|
const rdap = rdapResult.value;
|
|
@@ -172,6 +178,12 @@ export class IpIntelligence {
|
|
|
172
178
|
return result;
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
/** Retry and confirm cleanup of a standalone DNS client, if one is retained. */
|
|
182
|
+
public stop(): Promise<void> {
|
|
183
|
+
if (this.sharedDnsClient) return Promise.resolve();
|
|
184
|
+
return this.enqueueDnsLifecycle(() => this.terminateOwnedDnsClient());
|
|
185
|
+
}
|
|
186
|
+
|
|
175
187
|
// ─── RDAP Subsystem (delegated to @push.rocks/smartwhois) ───────────
|
|
176
188
|
|
|
177
189
|
/**
|
|
@@ -208,46 +220,68 @@ export class IpIntelligence {
|
|
|
208
220
|
* Response: "ASN | prefix | CC | rir | date"
|
|
209
221
|
*/
|
|
210
222
|
private async queryTeamCymru(ip: string): Promise<{ asn: number; prefix: string; country: string } | null> {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
const query = async (
|
|
224
|
+
dnsClientArg: InstanceType<typeof plugins.smartdns.dnsClientMod.Smartdns>,
|
|
225
|
+
) => {
|
|
226
|
+
try {
|
|
227
|
+
const reversed = ip.split('.').reverse().join('.');
|
|
228
|
+
const queryName = `${reversed}.origin.asn.cymru.com`;
|
|
229
|
+
const records = await dnsClientArg.getRecordsTxt(queryName);
|
|
230
|
+
if (!records || records.length === 0) return null;
|
|
231
|
+
|
|
232
|
+
// Parse the first TXT record: "13335 | 1.1.1.0/24 | AU | apnic | 2011-08-11"
|
|
233
|
+
const txt = records[0].value || (records[0] as any).data;
|
|
234
|
+
if (!txt) return null;
|
|
235
|
+
|
|
236
|
+
const parts = txt.split('|').map((s: string) => s.trim());
|
|
237
|
+
if (parts.length < 3) return null;
|
|
238
|
+
|
|
239
|
+
const asn = parseInt(parts[0], 10);
|
|
240
|
+
if (isNaN(asn)) return null;
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
asn,
|
|
244
|
+
prefix: parts[1] || '',
|
|
245
|
+
country: parts[2] || '',
|
|
246
|
+
};
|
|
247
|
+
} catch (err: any) {
|
|
248
|
+
this.logger.debug?.(`Team Cymru DNS query failed for ${ip}: ${err.message}`);
|
|
249
|
+
return null;
|
|
223
250
|
}
|
|
251
|
+
};
|
|
224
252
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// Parse the first TXT record: "13335 | 1.1.1.0/24 | AU | apnic | 2011-08-11"
|
|
229
|
-
const txt = records[0].value || (records[0] as any).data;
|
|
230
|
-
if (!txt) return null;
|
|
253
|
+
if (this.sharedDnsClient) {
|
|
254
|
+
return query(this.sharedDnsClient);
|
|
255
|
+
}
|
|
231
256
|
|
|
232
|
-
|
|
233
|
-
|
|
257
|
+
return this.enqueueDnsLifecycle(async () => {
|
|
258
|
+
await this.terminateOwnedDnsClient();
|
|
259
|
+
const dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
|
|
260
|
+
strategy: 'prefer-system',
|
|
261
|
+
allowDohFallback: true,
|
|
262
|
+
timeoutMs: this.timeout,
|
|
263
|
+
});
|
|
264
|
+
this.ownedDnsClient = dnsClient;
|
|
265
|
+
try {
|
|
266
|
+
return await query(dnsClient);
|
|
267
|
+
} finally {
|
|
268
|
+
await this.terminateOwnedDnsClient();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
234
272
|
|
|
235
|
-
|
|
236
|
-
|
|
273
|
+
private enqueueDnsLifecycle<T>(operationArg: () => Promise<T>): Promise<T> {
|
|
274
|
+
const result = this.dnsLifecycleQueue.then(operationArg);
|
|
275
|
+
this.dnsLifecycleQueue = result.then(() => {}, () => {});
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
237
278
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
this.logger.debug?.(`Team Cymru DNS query failed for ${ip}: ${err.message}`);
|
|
245
|
-
return null;
|
|
246
|
-
} finally {
|
|
247
|
-
// Only destroy clients we created ourselves; leave injected ones alone.
|
|
248
|
-
if (!external && dnsClient) {
|
|
249
|
-
dnsClient.destroy();
|
|
250
|
-
}
|
|
279
|
+
private async terminateOwnedDnsClient(): Promise<void> {
|
|
280
|
+
const dnsClient = this.ownedDnsClient;
|
|
281
|
+
if (!dnsClient) return;
|
|
282
|
+
await dnsClient.terminate();
|
|
283
|
+
if (this.ownedDnsClient === dnsClient) {
|
|
284
|
+
this.ownedDnsClient = null;
|
|
251
285
|
}
|
|
252
286
|
}
|
|
253
287
|
|
|
@@ -46,28 +46,30 @@ type TNetworkCommands = {
|
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
49
|
+
/** Internal marker for an activation failure whose cleanup still needs an owner. */
|
|
50
|
+
export class RustNetworkBridgeActivationCleanupError extends AggregateError {
|
|
51
|
+
constructor(activationErrorArg: unknown, cleanupErrorArg: unknown) {
|
|
52
|
+
super(
|
|
53
|
+
[activationErrorArg, cleanupErrorArg],
|
|
54
|
+
'RustNetworkBridge activation failed and cleanup could not be confirmed',
|
|
55
|
+
);
|
|
56
|
+
this.name = 'RustNetworkBridgeActivationCleanupError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The tsrust target of the shipped binary for this host: static musl builds for Linux amd64 and arm64
|
|
62
|
+
*/
|
|
63
|
+
function getDistRustTarget(): string | null {
|
|
59
64
|
const archMap: Record<string, string> = {
|
|
60
65
|
x64: 'amd64',
|
|
61
66
|
arm64: 'arm64',
|
|
62
67
|
};
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (p && a) {
|
|
68
|
-
return `${p}_${a}`;
|
|
68
|
+
const arch = archMap[process.arch];
|
|
69
|
+
if (process.platform !== 'linux' || !arch) {
|
|
70
|
+
return null;
|
|
69
71
|
}
|
|
70
|
-
return
|
|
72
|
+
return `linux_${arch}_musl`;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
|
@@ -85,6 +87,9 @@ export class RustNetworkBridge {
|
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
private bridge: InstanceType<typeof plugins.smartrust.RustBridge<TNetworkCommands>>;
|
|
90
|
+
private readonly owners = new Set<object>();
|
|
91
|
+
private cleanupRequired = false;
|
|
92
|
+
private lifecycleQueue: Promise<void> = Promise.resolve();
|
|
88
93
|
|
|
89
94
|
private constructor() {
|
|
90
95
|
const packageDir = path.resolve(
|
|
@@ -92,12 +97,12 @@ export class RustNetworkBridge {
|
|
|
92
97
|
'..',
|
|
93
98
|
);
|
|
94
99
|
|
|
95
|
-
const
|
|
100
|
+
const distRustTarget = getDistRustTarget();
|
|
96
101
|
const localPaths: string[] = [];
|
|
97
102
|
|
|
98
|
-
//
|
|
99
|
-
if (
|
|
100
|
-
localPaths.push(path.join(packageDir, 'dist_rust', `rustnetwork_${
|
|
103
|
+
// Shipped static binary for this host
|
|
104
|
+
if (distRustTarget) {
|
|
105
|
+
localPaths.push(path.join(packageDir, 'dist_rust', `rustnetwork_${distRustTarget}`));
|
|
101
106
|
}
|
|
102
107
|
// Native build without suffix
|
|
103
108
|
localPaths.push(path.join(packageDir, 'dist_rust', 'rustnetwork'));
|
|
@@ -115,28 +120,72 @@ export class RustNetworkBridge {
|
|
|
115
120
|
});
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const ok = await this.bridge.spawn();
|
|
123
|
-
if (!ok) {
|
|
124
|
-
throw new Error('Failed to spawn rustnetwork binary');
|
|
125
|
-
}
|
|
123
|
+
private enqueueLifecycle(operationArg: () => Promise<void>): Promise<void> {
|
|
124
|
+
const result = this.lifecycleQueue.then(operationArg);
|
|
125
|
+
this.lifecycleQueue = result.catch(() => {});
|
|
126
|
+
return result;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
/**
|
|
129
|
-
*
|
|
130
|
+
* Acquire the shared Rust binary and wait for it to be ready.
|
|
130
131
|
*/
|
|
131
|
-
public
|
|
132
|
-
this.
|
|
132
|
+
public start(ownerArg: object = this): Promise<void> {
|
|
133
|
+
return this.enqueueLifecycle(async () => {
|
|
134
|
+
if (this.cleanupRequired) {
|
|
135
|
+
throw new Error('RustNetworkBridge cleanup must succeed before it can start again');
|
|
136
|
+
}
|
|
137
|
+
if (this.owners.size > 0 && !this.bridge.running) {
|
|
138
|
+
throw new Error('RustNetworkBridge stopped while it still had active owners');
|
|
139
|
+
}
|
|
140
|
+
if (this.owners.has(ownerArg)) return;
|
|
141
|
+
if (this.owners.size === 0) {
|
|
142
|
+
let activationError: unknown;
|
|
143
|
+
try {
|
|
144
|
+
const ok = await this.bridge.spawn();
|
|
145
|
+
if (ok) {
|
|
146
|
+
this.owners.add(ownerArg);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
activationError = new Error('Failed to spawn rustnetwork binary');
|
|
150
|
+
} catch (error) {
|
|
151
|
+
activationError = error;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await this.bridge.terminate();
|
|
156
|
+
} catch (cleanupError) {
|
|
157
|
+
this.owners.add(ownerArg);
|
|
158
|
+
this.cleanupRequired = true;
|
|
159
|
+
throw new RustNetworkBridgeActivationCleanupError(
|
|
160
|
+
activationError,
|
|
161
|
+
cleanupError,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
throw activationError;
|
|
165
|
+
}
|
|
166
|
+
this.owners.add(ownerArg);
|
|
167
|
+
});
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
/**
|
|
136
|
-
*
|
|
171
|
+
* Release one owner and terminate the Rust binary after the last release.
|
|
137
172
|
*/
|
|
138
|
-
|
|
139
|
-
|
|
173
|
+
public stop(ownerArg: object = this): Promise<void> {
|
|
174
|
+
return this.enqueueLifecycle(async () => {
|
|
175
|
+
if (!this.owners.has(ownerArg)) return;
|
|
176
|
+
if (this.owners.size > 1) {
|
|
177
|
+
this.owners.delete(ownerArg);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
await this.bridge.terminate();
|
|
182
|
+
} catch (error) {
|
|
183
|
+
this.cleanupRequired = true;
|
|
184
|
+
throw error;
|
|
185
|
+
}
|
|
186
|
+
this.cleanupRequired = false;
|
|
187
|
+
this.owners.delete(ownerArg);
|
|
188
|
+
});
|
|
140
189
|
}
|
|
141
190
|
|
|
142
191
|
// ===== Command wrappers =====
|