feat(smartnetwork): Add randomizable port search and switch DNS resolution to @push.rocks/smartdns; export smartdns and update docs

This commit is contained in:
2025-09-12 17:59:06 +00:00
parent e70e5ac15c
commit b219ec2208
7 changed files with 382 additions and 132 deletions

View File

@@ -24,6 +24,15 @@ export interface Hop {
ip: string;
rtt: number | null;
}
/**
* Options for the findFreePort method
*/
export interface IFindFreePortOptions {
/** If true, selects a random available port within the range instead of the first one */
randomize?: boolean;
}
export class SmartNetwork {
/** Static registry for external plugins */
public static pluginsRegistry: Map<string, any> = new Map();
@@ -148,9 +157,10 @@ export class SmartNetwork {
* Find the first available port within a given range
* @param startPort The start of the port range (inclusive)
* @param endPort The end of the port range (inclusive)
* @returns The first available port number, or null if no ports are available
* @param options Optional configuration for port selection behavior
* @returns The first available port number (or random if options.randomize is true), or null if no ports are available
*/
public async findFreePort(startPort: number, endPort: number): Promise<number | null> {
public async findFreePort(startPort: number, endPort: number, options?: IFindFreePortOptions): Promise<number | null> {
// Validate port range
if (startPort < 1 || startPort > 65535 || endPort < 1 || endPort > 65535) {
throw new NetworkError('Port numbers must be between 1 and 65535', 'EINVAL');
@@ -159,16 +169,38 @@ export class SmartNetwork {
throw new NetworkError('Start port must be less than or equal to end port', 'EINVAL');
}
// Check each port in the range
for (let port = startPort; port <= endPort; port++) {
const isUnused = await this.isLocalPortUnused(port);
if (isUnused) {
return port;
// If randomize option is true, collect all available ports and select randomly
if (options?.randomize) {
const availablePorts: number[] = [];
// Scan the range to find all available ports
for (let port = startPort; port <= endPort; port++) {
const isUnused = await this.isLocalPortUnused(port);
if (isUnused) {
availablePorts.push(port);
}
}
// If there are available ports, select one randomly
if (availablePorts.length > 0) {
const randomIndex = Math.floor(Math.random() * availablePorts.length);
return availablePorts[randomIndex];
}
// No free port found in the range
return null;
} else {
// Default behavior: return the first available port (sequential search)
for (let port = startPort; port <= endPort; port++) {
const isUnused = await this.isLocalPortUnused(port);
if (isUnused) {
return port;
}
}
// No free port found in the range
return null;
}
// No free port found in the range
return null;
}
/**
@@ -277,13 +309,30 @@ export class SmartNetwork {
host: string,
): Promise<{ A: string[]; AAAA: string[]; MX: { exchange: string; priority: number }[] }> {
try {
const dns = await import('dns');
const { resolve4, resolve6, resolveMx } = dns.promises;
const [A, AAAA, MX] = await Promise.all([
resolve4(host).catch(() => []),
resolve6(host).catch(() => []),
resolveMx(host).catch(() => []),
const dnsClient = new plugins.smartdns.dnsClientMod.Smartdns({
strategy: 'prefer-system', // Try system resolver first (handles localhost), fallback to DoH
allowDohFallback: true,
});
const [aRecords, aaaaRecords, mxRecords] = await Promise.all([
dnsClient.getRecordsA(host).catch((): any[] => []),
dnsClient.getRecordsAAAA(host).catch((): any[] => []),
dnsClient.getRecords(host, 'MX').catch((): any[] => []),
]);
// Extract values from the record objects
const A = aRecords.map((record: any) => record.value);
const AAAA = aaaaRecords.map((record: any) => record.value);
// Parse MX records - the value contains "priority exchange"
const MX = mxRecords.map((record: any) => {
const parts = record.value.split(' ');
return {
priority: parseInt(parts[0], 10),
exchange: parts[1] || '',
};
});
return { A, AAAA, MX };
} catch (err: any) {
throw new NetworkError(err.message, err.code);