Add network identity diagnostics
This commit is contained in:
@@ -4,6 +4,7 @@ import net from 'node:net';
|
||||
import {
|
||||
assessConnectivity,
|
||||
CONNECTIVITY_IP_SOURCES,
|
||||
CONNECTIVITY_NETWORK_SOURCE,
|
||||
CONNECTIVITY_SITES,
|
||||
MAX_CUSTOM_DIAGNOSTIC_SERVICES,
|
||||
} from '../../shared/connectivityDiagnostics.js';
|
||||
@@ -66,6 +67,16 @@ interface IpProbeResult {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface NetworkProbeResult {
|
||||
address: string | null;
|
||||
asn: string | null;
|
||||
provider: string | null;
|
||||
city: string | null;
|
||||
country: string | null;
|
||||
attempts: number;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface SiteProbeResult {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -80,6 +91,7 @@ interface SiteProbeResult {
|
||||
}
|
||||
|
||||
type DiagnosticTarget =
|
||||
| { kind: 'network' }
|
||||
| { kind: 'ip'; probe: IpProbe }
|
||||
| { kind: 'site'; probe: SiteProbe };
|
||||
|
||||
@@ -261,6 +273,55 @@ async function publicIps(path: PathKind, proxyPort: number, execute: CurlExecuto
|
||||
};
|
||||
}
|
||||
|
||||
function text(value: unknown) {
|
||||
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
||||
}
|
||||
|
||||
function parseNetwork(body: string): Omit<NetworkProbeResult, 'attempts' | 'error'> | null {
|
||||
try {
|
||||
const value = record(JSON.parse(body));
|
||||
const connection = record(value.connection);
|
||||
const address = text(value.ip);
|
||||
if (value.success === false || !address || net.isIP(address) !== 4) return null;
|
||||
const number = Number(connection.asn);
|
||||
return {
|
||||
address,
|
||||
asn: Number.isSafeInteger(number) && number > 0 ? `AS${number}` : null,
|
||||
provider: text(connection.isp) || text(connection.org),
|
||||
city: text(value.city),
|
||||
country: text(value.country_code) || text(value.country),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function networkProbe(
|
||||
path: PathKind,
|
||||
proxyPort: number,
|
||||
execute: CurlExecutor,
|
||||
sampleCount = 1,
|
||||
): Promise<NetworkProbeResult> {
|
||||
const samples: Array<RequestResult & { network: ReturnType<typeof parseNetwork> }> = [];
|
||||
for (let attempt = 0; attempt < sampleCount; attempt += 1) {
|
||||
const result = await request(CONNECTIVITY_NETWORK_SOURCE, path, proxyPort, execute, { body: true, ipv4: true });
|
||||
samples.push({ ...result, network: result.ok ? parseNetwork(result.body) : null });
|
||||
}
|
||||
const selected = mostCommon(samples
|
||||
.map(({ network }) => network && JSON.stringify(network))
|
||||
.filter((value): value is string => Boolean(value)));
|
||||
const network = selected ? JSON.parse(selected) as ReturnType<typeof parseNetwork> : null;
|
||||
return {
|
||||
address: network?.address || null,
|
||||
asn: network?.asn || null,
|
||||
provider: network?.provider || null,
|
||||
city: network?.city || null,
|
||||
country: network?.country || null,
|
||||
attempts: samples.length,
|
||||
error: network ? null : samples.at(-1)?.error || 'invalid network response',
|
||||
};
|
||||
}
|
||||
|
||||
function isPublicAddress(address: string, family: number) {
|
||||
const type = family === 4 ? 'ipv4' : family === 6 ? 'ipv6' : '';
|
||||
const blocked = family === 4 ? BLOCKED_IPV4_ADDRESSES : BLOCKED_IPV6_ADDRESSES;
|
||||
@@ -361,7 +422,8 @@ async function probePath(
|
||||
execute: CurlExecutor,
|
||||
sites: SiteProbe[],
|
||||
): Promise<ConnectivityPathResult> {
|
||||
const [ip, siteResults] = await Promise.all([
|
||||
const [network, ip, siteResults] = await Promise.all([
|
||||
networkProbe(path, proxyPort, execute),
|
||||
publicIps(path, proxyPort, execute),
|
||||
Promise.all(sites.map((probe) => siteProbe(probe, path, proxyPort, execute))),
|
||||
]);
|
||||
@@ -370,6 +432,7 @@ async function probePath(
|
||||
internetAvailable: Boolean(
|
||||
ip.ipv4.addresses.length || ip.ipv6 || siteResults.some((site) => site.status !== 'unavailable'),
|
||||
),
|
||||
network,
|
||||
...ip,
|
||||
sites: siteResults,
|
||||
};
|
||||
@@ -389,6 +452,7 @@ function unavailablePath(): ConnectivityPathResult {
|
||||
|
||||
function resolveTarget(targetId: unknown, sites: SiteProbe[]): DiagnosticTarget | null {
|
||||
if (typeof targetId !== 'string') return null;
|
||||
if (targetId === CONNECTIVITY_NETWORK_SOURCE.id) return { kind: 'network' };
|
||||
if (targetId.startsWith('ip:')) {
|
||||
const probe = IP_PROBES.find(({ id }) => id === targetId.slice(3));
|
||||
return probe ? { kind: 'ip', probe } : null;
|
||||
@@ -406,6 +470,9 @@ async function probeTarget(
|
||||
proxyPort: number,
|
||||
execute: CurlExecutor,
|
||||
): Promise<ConnectivityPathResult> {
|
||||
const network = target.kind === 'network'
|
||||
? await networkProbe(path, proxyPort, execute, TARGET_SAMPLE_COUNT)
|
||||
: null;
|
||||
const ip = target.kind === 'ip'
|
||||
? await ipProbe(target.probe, path, proxyPort, execute, TARGET_SAMPLE_COUNT)
|
||||
: null;
|
||||
@@ -417,7 +484,8 @@ async function probeTarget(
|
||||
const sites = site ? [site] : [];
|
||||
return {
|
||||
available: true,
|
||||
internetAvailable: Boolean(ip?.address || (site && site.status !== 'unavailable')),
|
||||
internetAvailable: Boolean(network?.address || ip?.address || (site && site.status !== 'unavailable')),
|
||||
...(network ? { network } : {}),
|
||||
ipv4: {
|
||||
addresses: ipv4Sources.map(({ address }) => address).filter((value): value is string => Boolean(value)),
|
||||
sources: ipv4Sources,
|
||||
|
||||
Reference in New Issue
Block a user