@mark1russell7/netdiag 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +244 -2
- package/build/address.d.ts +56 -0
- package/build/address.d.ts.map +1 -0
- package/build/address.js +172 -0
- package/build/address.js.map +1 -0
- package/build/diagnosis-a4hRzFxp.js +922 -0
- package/build/diagnosis-a4hRzFxp.js.map +1 -0
- package/build/diagnosis.d.ts +8 -0
- package/build/diagnosis.js +1 -0
- package/build/index-K2dGiYxW.d.ts +259 -0
- package/build/index-K2dGiYxW.d.ts.map +1 -0
- package/build/index.d.ts +392 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +1359 -0
- package/build/index.js.map +1 -0
- package/build/kinds-CQv769lK.js +341 -0
- package/build/kinds-CQv769lK.js.map +1 -0
- package/build/kinds-_NShfZpU.d.ts +377 -0
- package/build/kinds-_NShfZpU.d.ts.map +1 -0
- package/build/layer-BNg0VYuK.d.ts +22 -0
- package/build/layer-BNg0VYuK.d.ts.map +1 -0
- package/build/tools.d.ts +206 -0
- package/build/tools.d.ts.map +1 -0
- package/build/tools.js +896 -0
- package/build/tools.js.map +1 -0
- package/package.json +81 -3
- package/src/address/index.ts +155 -0
- package/src/diagnosis/catalog.ts +357 -0
- package/src/diagnosis/index.ts +47 -0
- package/src/diagnosis/model.ts +322 -0
- package/src/diagnosis/score.ts +160 -0
- package/src/fact.ts +87 -0
- package/src/index.ts +68 -0
- package/src/kinds.ts +460 -0
- package/src/layer.ts +31 -0
- package/src/probe.ts +61 -0
- package/src/probes/native.ts +354 -0
- package/src/probes/socket.ts +520 -0
- package/src/readers.ts +367 -0
- package/src/run.ts +163 -0
- package/src/scope.ts +152 -0
- package/src/target.ts +116 -0
- package/src/tools/common.ts +47 -0
- package/src/tools/host.ts +91 -0
- package/src/tools/index.ts +38 -0
- package/src/tools/licenses.ts +37 -0
- package/src/tools/mtr.ts +156 -0
- package/src/tools/ping.ts +231 -0
- package/src/tools/traceroute.ts +313 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["netConnect"],"sources":["../src/scope.ts","../src/run.ts","../src/probes/socket.ts","../src/probes/native.ts","../src/readers.ts"],"sourcesContent":["import { isIP } from \"node:net\";\nimport { err, ok, type Result } from \"@mark1russell7/optional\";\nimport { cidrContains, parseCidr } from \"./address/index.ts\";\nimport { canonicalHost, formatTarget, type Target } from \"./target.ts\";\n\n/**\n * The basis of a grant: why a target is in the scope.\n *\n * - `named`: the user named the target for this diagnosis.\n * - `owned`: the user declares that the user owns the network or the host.\n * - `configured`: the configuration of the computer of the user names it, for example the gateway or the DNS resolver.\n * - `resolved`: a DNS answer for a granted name gave this address.\n */\nexport type GrantBasis = \"named\" | \"owned\" | \"configured\" | \"resolved\";\n\n/**\n * A grant puts targets in the scope.\n *\n * A `host` grant puts a DNS name in the scope, and with `subdomains` each name under it. A `range` grant puts the\n * addresses of a CIDR range in the scope, for example `192.0.2.0/24` or `2001:db8::/32`.\n */\nexport type Grant = HostGrant | RangeGrant;\n\n/** A grant of a DNS name, and with `subdomains` of each name under it. */\nexport interface HostGrant {\n readonly kind: \"host\";\n readonly host: string;\n readonly subdomains?: boolean | undefined;\n readonly basis: GrantBasis;\n readonly note?: string | undefined;\n}\n\n/** A grant of the addresses of a CIDR range, for example `192.0.2.0/24` or `2001:db8::/32`. */\nexport interface RangeGrant {\n readonly kind: \"range\";\n readonly cidr: string;\n readonly basis: GrantBasis;\n readonly note?: string | undefined;\n}\n\n/**\n * The scope of a diagnosis: the grants, and the largest number of ports for each host.\n *\n * An active probe sends packets only to a peer in the scope. The limit of ports stops a port scan: netdiag does no\n * vulnerability scan.\n */\nexport interface Scope {\n readonly grants: readonly Grant[];\n readonly maxPortsPerHost: number;\n}\n\n/** The default limit of the different ports of one host in one runner. */\nexport const DEFAULT_MAX_PORTS_PER_HOST = 8;\n\n/** The decision of the scope for one peer of an active probe. A refusal has the reason in plain words. */\nexport type ScopeDecision =\n | { readonly allowed: true; readonly grant: Grant }\n | { readonly allowed: false; readonly reason: string };\n\n/** The ports that a runner sent packets to, for each host. */\nexport type PortLedger = ReadonlyMap<string, ReadonlySet<number>>;\n\n/** This function gives the problems of a grant, or an empty list for a correct grant. */\nexport function grantProblems(grant: Grant): string[] {\n if (grant.kind === \"range\") {\n const range = parseCidr(grant.cidr);\n return range.tag === \"err\" ? [range.error] : [];\n }\n const host = canonicalHost(grant.host);\n if (host.length === 0) return [\"A host grant has an empty host.\"];\n if (isIP(host) !== 0) return [`The host grant \"${grant.host}\" is an IP address. Use a range grant for an address.`];\n return [];\n}\n\n/**\n * This function makes a scope from grants. It refuses a grant with a problem, so a typing error cannot widen the scope.\n *\n * The default limit of ports is `DEFAULT_MAX_PORTS_PER_HOST`.\n */\nexport function scopeOf(grants: readonly Grant[], options: { readonly maxPortsPerHost?: number } = {}): Result<Scope, string[]> {\n const problems = grants.flatMap(grantProblems);\n const maxPortsPerHost = options.maxPortsPerHost ?? DEFAULT_MAX_PORTS_PER_HOST;\n if (!Number.isInteger(maxPortsPerHost) || maxPortsPerHost < 1) problems.push(\"The limit of ports for each host is not a positive integer.\");\n if (problems.length > 0) return err(problems);\n return ok({ grants: [...grants], maxPortsPerHost });\n}\n\n/**\n * This function gives the grant for a target that the user names. A name gets a host grant. An IP address gets a\n * range grant of one address.\n */\nexport function namedGrant(t: Target, note?: string): Grant {\n if (t.kind === \"name\") return { kind: \"host\", host: t.host, basis: \"named\", note };\n return { kind: \"range\", cidr: `${t.host}/${t.kind === \"ipv4\" ? 32 : 128}`, basis: \"named\", note };\n}\n\n/** This function gives the grant of one address that a DNS answer for a granted name gave. */\nexport function resolvedGrant(name: string, address: string, factId?: string): RangeGrant {\n const host = canonicalHost(address);\n const family = isIP(host);\n if (family === 0) throw new RangeError(`\"${address}\" is not an IP address`);\n const note = factId === undefined ? `${canonicalHost(name)} resolved to it` : `${canonicalHost(name)} resolved to it (fact ${factId})`;\n return { kind: \"range\", cidr: `${host}/${family === 4 ? 32 : 128}`, basis: \"resolved\", note };\n}\n\n/** This function gives a scope with more grants. The limit of ports does not change. */\nexport function widen(scope: Scope, grants: readonly Grant[]): Result<Scope, string[]> {\n return scopeOf([...scope.grants, ...grants], { maxPortsPerHost: scope.maxPortsPerHost });\n}\n\nfunction grantFor(scope: Scope, peer: Target): Grant | undefined {\n const host = canonicalHost(peer.host);\n const family = isIP(host);\n for (const grant of scope.grants) {\n if (grant.kind === \"host\") {\n if (family !== 0) continue;\n const granted = canonicalHost(grant.host);\n if (host === granted || (grant.subdomains === true && host.endsWith(`.${granted}`))) return grant;\n continue;\n }\n if (family === 0) continue;\n const range = parseCidr(grant.cidr);\n if (range.tag === \"ok\" && cidrContains(range.value, host)) return grant;\n }\n return undefined;\n}\n\n/**\n * This function decides if an active probe can send packets to a peer.\n *\n * The peer must match a grant of the scope. A new port of a host must also stay in the limit of ports for each host.\n * The ledger has the ports that the runner sent packets to before.\n */\nexport function decideScope(scope: Scope, peer: Target, ledger: PortLedger = new Map()): ScopeDecision {\n const grant = grantFor(scope, peer);\n if (grant === undefined) {\n return {\n allowed: false,\n reason: `${formatTarget(peer)} is not in the scope. Name it, or add a grant for a network that you own.`,\n };\n }\n if (peer.port !== undefined) {\n const ports = ledger.get(canonicalHost(peer.host)) ?? new Set<number>();\n if (!ports.has(peer.port) && ports.size >= scope.maxPortsPerHost) {\n return {\n allowed: false,\n reason: `${formatTarget(peer)} is a new port after ${ports.size} ports of the same host. netdiag does no port scan.`,\n };\n }\n }\n return { allowed: true, grant };\n}\n","import type { Clock } from \"@mark1russell7/algebra/clock\";\nimport type { Fact, FactTime, Observation } from \"./fact.ts\";\nimport type { Probe, ProbeContext } from \"./probe.ts\";\nimport { decideScope, widen, type Grant, type PortLedger, type Scope } from \"./scope.ts\";\nimport { canonicalHost, formatTarget, type Target } from \"./target.ts\";\n\n/**\n * The outcome of one run of a probe.\n *\n * - `done`: the probe did its work. The facts include each network failure that it saw.\n * - `refused`: a peer is not in the scope, so the probe did not start.\n * - `unavailable`: the probe cannot operate on this computer.\n * - `failed`: the probe or its parser has a fault. The raw output is present when the run ended.\n * - `aborted`: the signal stopped the run.\n */\nexport type ProbeOutcome<R> =\n | { readonly status: \"done\"; readonly time: FactTime; readonly raw: R; readonly facts: readonly Fact[] }\n | { readonly status: \"refused\"; readonly peer: Target; readonly reason: string }\n | { readonly status: \"unavailable\"; readonly reason: string }\n | { readonly status: \"failed\"; readonly stage: \"probe\" | \"run\" | \"parse\"; readonly error: string; readonly raw?: R | undefined }\n | { readonly status: \"aborted\"; readonly reason: string };\n\n/** The wall-clock time of one monotonic time of the runner. */\nexport interface ClockOrigin {\n readonly wall: string;\n readonly monotonic: number;\n}\n\n/** The options of a runner. */\nexport interface RunnerOptions {\n readonly scope: Scope;\n readonly clock: Clock;\n /** The wall-clock time of the start, as an ISO 8601 text. The default is the time of the computer. */\n readonly wall?: string | undefined;\n}\n\n/**\n * A runner starts probes in one scope, and keeps the facts of the diagnosis.\n *\n * It gives each fact an id in sequence. It keeps the ledger of the ports of each host, for the limit of ports of the scope.\n */\nexport interface Runner {\n readonly origin: ClockOrigin;\n scope(): Scope;\n facts(): readonly Fact[];\n /** This method adds grants to the scope, for example the addresses of a granted name. */\n grant(grants: readonly Grant[]): void;\n run<I, R>(probe: Probe<I, R>, input: I, signal?: AbortSignal): Promise<ProbeOutcome<R>>;\n}\n\nfunction message(thrown: unknown): string {\n if (thrown instanceof Error) return thrown.message;\n return String(thrown);\n}\n\nfunction abortReason(signal: AbortSignal): string {\n const reason: unknown = signal.reason;\n return reason === undefined ? \"The signal stopped the run.\" : message(reason);\n}\n\n/** This function makes a runner. */\nexport function createRunner(options: RunnerOptions): Runner {\n const { clock } = options;\n const origin: ClockOrigin = { wall: options.wall ?? new Date().toISOString(), monotonic: clock.now() };\n let scope = options.scope;\n const facts: Fact[] = [];\n let ledger: PortLedger = new Map();\n let next = 1;\n\n /** This function gives the ledger with the ports of the peers, or the refusal of the first peer out of the scope. */\n function reserve(peers: readonly Target[]): PortLedger | { readonly peer: Target; readonly reason: string } {\n const planned = new Map([...ledger].map(([host, ports]) => [host, new Set(ports)]));\n for (const peer of peers) {\n const decision = decideScope(scope, peer, planned);\n if (!decision.allowed) return { peer, reason: decision.reason };\n if (peer.port === undefined) continue;\n const host = canonicalHost(peer.host);\n planned.set(host, (planned.get(host) ?? new Set<number>()).add(peer.port));\n }\n return planned;\n }\n\n function stamp<I, R>(probe: Probe<I, R>, observations: readonly Observation[], time: FactTime): Fact[] | string {\n const stamped: Fact[] = [];\n for (const observation of observations) {\n const { kind } = observation;\n if (!probe.kinds.some((known) => known.kind === kind.kind)) return `The probe gave the kind \"${kind.kind}\", which it does not declare.`;\n if (!kind.check(observation.value)) return `The value of a \"${kind.kind}\" fact does not pass the check of its kind.`;\n stamped.push({\n id: `f${next + stamped.length}`,\n kind: kind.kind,\n layer: kind.layer,\n subject: observation.subject,\n time: observation.time ?? time,\n source: probe.tool === undefined ? { probe: probe.id, version: probe.version } : { probe: probe.id, version: probe.version, tool: probe.tool },\n value: observation.value,\n });\n }\n return stamped;\n }\n\n async function run<I, R>(probe: Probe<I, R>, input: I, signal: AbortSignal = new AbortController().signal): Promise<ProbeOutcome<R>> {\n if (signal.aborted) return { status: \"aborted\", reason: abortReason(signal) };\n let peers: readonly Target[];\n try {\n peers = probe.peers(input);\n } catch (thrown) {\n return { status: \"failed\", stage: \"probe\", error: message(thrown) };\n }\n if (probe.mode === \"passive\" && peers.length > 0) {\n return { status: \"failed\", stage: \"probe\", error: `The passive probe \"${probe.id}\" gives peers: ${peers.map(formatTarget).join(\", \")}.` };\n }\n if (probe.mode === \"active\" && peers.length === 0) {\n return { status: \"failed\", stage: \"probe\", error: `The active probe \"${probe.id}\" gives no peer.` };\n }\n // The decision and the reservation of the ports happen with no await between them. Thus two runs at the same time\n // cannot both pass the limit of ports. A run that does not start keeps its reservation: the ledger only grows.\n const reserved = reserve(peers);\n if (\"reason\" in reserved) return { status: \"refused\", peer: reserved.peer, reason: reserved.reason };\n ledger = reserved;\n const context: ProbeContext = { clock, signal };\n let availability;\n try {\n availability = await probe.available(context);\n } catch (thrown) {\n return { status: \"failed\", stage: \"probe\", error: message(thrown) };\n }\n if (!availability.available) return { status: \"unavailable\", reason: availability.reason };\n if (signal.aborted) return { status: \"aborted\", reason: abortReason(signal) };\n const start = clock.now();\n let raw: R;\n try {\n raw = await probe.run(input, context);\n } catch (thrown) {\n if (signal.aborted) return { status: \"aborted\", reason: abortReason(signal) };\n return { status: \"failed\", stage: \"run\", error: message(thrown) };\n }\n const time: FactTime = { start, end: clock.now() };\n let observations: readonly Observation[];\n try {\n observations = probe.parse(raw, input);\n } catch (thrown) {\n return { status: \"failed\", stage: \"parse\", error: message(thrown), raw };\n }\n const stamped = stamp(probe, observations, time);\n if (typeof stamped === \"string\") return { status: \"failed\", stage: \"parse\", error: stamped, raw };\n facts.push(...stamped);\n next += stamped.length;\n return { status: \"done\", time, raw, facts: stamped };\n }\n\n return {\n origin,\n scope: () => scope,\n facts: () => [...facts],\n grant(grants) {\n const widened = widen(scope, grants);\n if (widened.tag === \"err\") throw new RangeError(widened.error.join(\" \"));\n scope = widened.value;\n },\n run,\n };\n}\n","import { X509Certificate } from \"node:crypto\";\nimport { request } from \"node:http\";\nimport { connect as netConnect, isIP, type Socket, type TcpNetConnectOpts } from \"node:net\";\nimport * as tls from \"node:tls\";\nimport type { Clock } from \"@mark1russell7/algebra/clock\";\nimport { observe, type FactTime, type Observation } from \"../fact.ts\";\nimport {\n HTTP_RESPONSE,\n TCP_CONNECT,\n TLS_HANDSHAKE,\n type CertProblem,\n type ConnectOutcome,\n type HandshakeOutcome,\n type HttpOutcome,\n type TlsCertificate,\n} from \"../kinds.ts\";\nimport type { ProbeContext } from \"../probe.ts\";\nimport { target, type Target } from \"../target.ts\";\n\n/** The function that opens a TCP socket. A test can give a function that gives a socket with no connection. */\nexport type Dial = (options: TcpNetConnectOpts) => Socket;\n\n/** The options of the native probes that connect. */\nexport interface ConnectOptions {\n /** The function that opens a TCP socket. The default is `connect` of `node:net`. */\n readonly dial?: Dial | undefined;\n}\n\n/** The time limit of a probe run, when its input gives none: 5 seconds. */\nexport const DEFAULT_TIMEOUT_MS = 5000;\n\nconst defaultDial: Dial = (options) => netConnect(options);\n\n/** This function gives the dial of the options. */\nexport function dialOf(options: ConnectOptions): Dial {\n return options.dial ?? defaultDial;\n}\n\n/** This function gives the error code of a thrown value, for example `ECONNREFUSED`. */\nexport function codeOf(thrown: unknown): string | undefined {\n if (typeof thrown !== \"object\" || thrown === null) return undefined;\n const code: unknown = (thrown as { code?: unknown }).code;\n return typeof code === \"string\" && code.length > 0 ? code : undefined;\n}\n\nfunction syscallOf(thrown: unknown): string | undefined {\n if (typeof thrown !== \"object\" || thrown === null) return undefined;\n const syscall: unknown = (thrown as { syscall?: unknown }).syscall;\n return typeof syscall === \"string\" ? syscall : undefined;\n}\n\n/** This function gives the peer of a probe that connects. It throws for an address that is not an IP address. */\nexport function peerOf(address: string, port: number): Target {\n if (isIP(address) === 0) {\n throw new TypeError(`\"${address}\" is not an IP address. Get the addresses of a name with the DNS probe first.`);\n }\n if (!Number.isInteger(port) || port < 1 || port > 65535) throw new RangeError(`${port} is not a port from 1 to 65535.`);\n return target(address, port);\n}\n\n/** This function gives the time limit of an input. It throws for a limit that is not a positive number. */\nexport function timeoutOf(timeoutMs: number | undefined): number {\n const limit = timeoutMs ?? DEFAULT_TIMEOUT_MS;\n if (!Number.isFinite(limit) || limit <= 0) throw new RangeError(`The time limit ${limit} is not a positive number of milliseconds.`);\n return limit;\n}\n\n/**\n * The deadline and the signal of one probe run.\n *\n * The watch destroys the current socket when the deadline passes or when the signal stops the run. The phase that\n * waits on the socket then gives the outcome `timeout`.\n */\nexport interface Watch {\n expired(): boolean;\n guard(socket: Socket): void;\n close(): void;\n}\n\n/** This function starts the watch of a probe run. */\nexport function watch(context: ProbeContext, timeoutMs: number): Watch {\n let expired = false;\n let current: Socket | undefined;\n const stop = (): void => {\n current?.destroy();\n };\n const cancel = context.clock.after(timeoutMs, () => {\n expired = true;\n stop();\n });\n context.signal.addEventListener(\"abort\", stop, { once: true });\n return {\n expired: () => expired,\n guard(socket) {\n current = socket;\n if (expired || context.signal.aborted) socket.destroy();\n },\n close() {\n cancel();\n context.signal.removeEventListener(\"abort\", stop);\n },\n };\n}\n\nconst ignore = (): void => undefined;\n\n/** This function maps an error code of a TCP connect to its outcome. */\nexport function connectOutcome(code: string | undefined): ConnectOutcome {\n switch (code) {\n case \"ECONNREFUSED\":\n return \"refused\";\n case \"ETIMEDOUT\":\n return \"timeout\";\n case \"EHOSTUNREACH\":\n case \"ENETUNREACH\":\n case \"EHOSTDOWN\":\n case \"ENETDOWN\":\n return \"unreachable\";\n case \"ECONNRESET\":\n return \"reset\";\n default:\n return \"error\";\n }\n}\n\n/**\n * This function tells how a stream ended before its phase was complete.\n *\n * Node.js gives the code `ECONNRESET` for a TCP reset, with the system call that read it. It gives the same code with\n * no system call when the peer closed the connection with a FIN.\n */\nexport function endOf(thrown: unknown, expired: boolean): \"timeout\" | \"reset\" | \"closed\" | undefined {\n if (expired) return \"timeout\";\n const code = codeOf(thrown);\n if (code === \"EPIPE\") return \"reset\";\n if (code !== \"ECONNRESET\") return undefined;\n return syscallOf(thrown) === undefined ? \"closed\" : \"reset\";\n}\n\nconst NOT_TLS = new Set([\"ERR_SSL_WRONG_VERSION_NUMBER\", \"ERR_SSL_PACKET_LENGTH_TOO_LONG\", \"ERR_SSL_HTTP_REQUEST\", \"ERR_SSL_RECORD_LAYER_FAILURE\"]);\nconst MISMATCH = new Set([\"ERR_SSL_UNSUPPORTED_PROTOCOL\", \"ERR_SSL_NO_PROTOCOLS_AVAILABLE\", \"ERR_SSL_NO_SHARED_CIPHER\", \"ERR_SSL_WRONG_SSL_VERSION\"]);\n\n/** This function maps an error of a TLS handshake to its outcome. */\nexport function handshakeOutcome(thrown: unknown, expired: boolean): HandshakeOutcome {\n const end = endOf(thrown, expired);\n if (end !== undefined) return end;\n const code = codeOf(thrown) ?? \"\";\n if (NOT_TLS.has(code)) return \"not-tls\";\n if (code.includes(\"_ALERT_\") || MISMATCH.has(code)) return \"alert\";\n return \"error\";\n}\n\n/** This function maps the authorization error of a TLS socket to the problem of the certificate. */\nexport function certProblem(code: string): CertProblem {\n switch (code) {\n case \"CERT_HAS_EXPIRED\":\n return \"expired\";\n case \"CERT_NOT_YET_VALID\":\n return \"not-yet-valid\";\n case \"ERR_TLS_CERT_ALTNAME_INVALID\":\n case \"HOSTNAME_MISMATCH\":\n return \"name\";\n case \"DEPTH_ZERO_SELF_SIGNED_CERT\":\n case \"SELF_SIGNED_CERT_IN_CHAIN\":\n case \"UNABLE_TO_GET_ISSUER_CERT\":\n case \"UNABLE_TO_GET_ISSUER_CERT_LOCALLY\":\n case \"UNABLE_TO_VERIFY_LEAF_SIGNATURE\":\n case \"CERT_UNTRUSTED\":\n return \"untrusted\";\n case \"CERT_REVOKED\":\n return \"revoked\";\n default:\n return \"other\";\n }\n}\n\n/** The TCP phase of a probe run. The times are readings of the clock of the runner. */\nexport interface TcpPhase {\n readonly address: string;\n readonly port: number;\n readonly family: 4 | 6;\n readonly start: number;\n readonly end: number;\n readonly outcome: ConnectOutcome;\n readonly code?: string | undefined;\n}\n\n/** This function connects to an address and a port, and gives the socket when it connected. */\nexport function connectPhase(dial: Dial, address: string, port: number, w: Watch, clock: Clock): Promise<{ socket?: Socket; phase: TcpPhase }> {\n const family = isIP(address) === 6 ? 6 : 4;\n const start = clock.now();\n return new Promise((resolve) => {\n const socket = dial({ host: address, port, family });\n socket.on(\"error\", ignore);\n let done = false;\n const finish = (outcome: ConnectOutcome, code?: string): void => {\n if (done) return;\n done = true;\n const phase: TcpPhase = { address, port, family, start, end: clock.now(), outcome, ...(code === undefined ? {} : { code }) };\n if (outcome === \"connected\") resolve({ socket, phase });\n else {\n socket.destroy();\n resolve({ phase });\n }\n };\n socket.once(\"connect\", () => finish(\"connected\"));\n socket.once(\"error\", (error) => (w.expired() ? finish(\"timeout\") : finish(connectOutcome(codeOf(error)), codeOf(error))));\n socket.once(\"close\", () => (w.expired() ? finish(\"timeout\") : finish(\"error\", \"ECLOSED\")));\n w.guard(socket);\n });\n}\n\n/** This function gives the observation of a TCP phase. */\nexport function tcpObservation(phase: TcpPhase): Observation {\n const { start, end, ...rest } = phase;\n return observe(TCP_CONNECT, target(phase.address, phase.port), { ...rest, ms: end - start }, { start, end });\n}\n\n/** The trust of the certificates of a TLS probe: the context, and the SHA-256 fingerprints of its roots. */\nexport interface TrustStore {\n readonly context: tls.SecureContext;\n readonly publicRoots: ReadonlySet<string>;\n /** The roots of the computer and of the user that are not public roots. */\n readonly privateRoots: ReadonlySet<string>;\n}\n\nfunction systemRoots(): readonly string[] {\n // `getCACertificates` is in Node.js 22.15 and later.\n const get: unknown = (tls as { getCACertificates?: unknown }).getCACertificates;\n if (typeof get !== \"function\") return [];\n try {\n const roots: unknown = get.call(tls, \"system\");\n return Array.isArray(roots) ? roots.filter((root): root is string => typeof root === \"string\") : [];\n } catch {\n return [];\n }\n}\n\n/**\n * This function makes the store of trust of a TLS probe. It has the public roots of Node.js, the roots of the computer\n * and the roots that the user gives. A chain that ends at a public root has the trust `public`. A chain that ends at a\n * different root of the store has the trust `private`, for example the root of a proxy that examines TLS. The roots\n * in `extraPublic` count as public roots.\n */\nexport function trustStore(extraRoots: readonly string[] = [], extraPublic: readonly string[] = []): TrustStore {\n const local = [...systemRoots(), ...extraRoots];\n const publicRoots = new Set([...tls.rootCertificates, ...extraPublic].flatMap(fingerprintOf));\n const privateRoots = new Set(local.flatMap(fingerprintOf).filter((fingerprint) => !publicRoots.has(fingerprint)));\n const context = tls.createSecureContext({ ca: [...tls.rootCertificates, ...extraPublic, ...local] });\n return { context, publicRoots, privateRoots };\n}\n\nfunction fingerprintOf(pem: string): string[] {\n try {\n return [new X509Certificate(pem).fingerprint256];\n } catch {\n return [];\n }\n}\n\nfunction text(value: string | string[] | undefined): string {\n if (value === undefined) return \"\";\n return Array.isArray(value) ? value.join(\", \") : value;\n}\n\nfunction isoTime(value: string): string {\n const time = new Date(value);\n return Number.isNaN(time.getTime()) ? value : time.toISOString();\n}\n\nfunction namesOf(subjectAltName: string | undefined): string[] {\n if (subjectAltName === undefined || subjectAltName.length === 0) return [];\n return subjectAltName\n .split(\", \")\n .map((entry) => entry.replace(/^(?:DNS|IP Address):/, \"\"))\n .filter((entry) => entry.length > 0);\n}\n\n// Node.js links a certificate to itself only when the check of the chain found it as a root. Thus this function\n// examines the certificate: its issuer is its subject, and its own key verifies its signature. It does not use\n// `checkIssued`, because OpenSSL then also requires the key usage `keyCertSign`, and a server certificate has none.\nfunction isSelfSigned(raw: Buffer): boolean {\n try {\n const certificate = new X509Certificate(raw);\n return certificate.issuer === certificate.subject && certificate.verify(certificate.publicKey);\n } catch {\n return false;\n }\n}\n\nfunction certificateOf(socket: tls.TLSSocket, store: TrustStore): TlsCertificate | undefined {\n const leaf = socket.getPeerCertificate(true);\n if (Object.keys(leaf).length === 0) return undefined;\n let top: tls.DetailedPeerCertificate = leaf;\n let chain = 1;\n const seen = new Set([leaf.fingerprint256]);\n while (top.issuerCertificate !== undefined && !seen.has(top.issuerCertificate.fingerprint256)) {\n top = top.issuerCertificate;\n seen.add(top.fingerprint256);\n chain += 1;\n }\n const authorized = socket.authorized;\n // The trust names the root of the chain, also when the check of the certificate fails. Thus an expired certificate of\n // a public authority keeps the trust `public`, and the certificate of a captive portal gets `none`.\n const trust = store.publicRoots.has(top.fingerprint256) ? \"public\" : store.privateRoots.has(top.fingerprint256) || authorized ? \"private\" : \"none\";\n const reason: unknown = socket.authorizationError;\n return {\n subject: text(leaf.subject.CN) || text(leaf.subject.O),\n issuer: text(leaf.issuer.CN) || text(leaf.issuer.O),\n names: namesOf(leaf.subjectaltname),\n validFrom: isoTime(leaf.valid_from),\n validTo: isoTime(leaf.valid_to),\n fingerprint256: leaf.fingerprint256,\n selfSigned: isSelfSigned(leaf.raw),\n chain,\n trust,\n ...(authorized ? {} : { problem: certProblem(reason instanceof Error ? (codeOf(reason) ?? reason.message) : String(reason)) }),\n };\n}\n\n/** The TLS phase of a probe run. */\nexport interface TlsPhase {\n readonly servername?: string | undefined;\n readonly start: number;\n readonly end: number;\n readonly outcome: HandshakeOutcome;\n readonly protocol?: string | undefined;\n readonly cipher?: string | undefined;\n readonly alpn?: string | undefined;\n readonly code?: string | undefined;\n readonly certificate?: TlsCertificate | undefined;\n}\n\n/** The input of a TLS handshake. */\nexport interface HandshakeInput {\n readonly address: string;\n /** The name for SNI and for the check of the certificate. With no name, the check uses the address. */\n readonly servername?: string | undefined;\n readonly alpn?: readonly string[] | undefined;\n}\n\n/**\n * This function does a TLS handshake on a connected socket. It does not refuse a certificate that is not valid: it\n * gives the outcome `cert-error` and the problem of the certificate.\n */\nexport function handshakePhase(\n socket: Socket,\n input: HandshakeInput,\n store: TrustStore,\n w: Watch,\n clock: Clock,\n): Promise<{ secure?: tls.TLSSocket; phase: TlsPhase }> {\n const start = clock.now();\n const servername = input.servername !== undefined && isIP(input.servername) === 0 ? input.servername : undefined;\n return new Promise((resolve) => {\n const secure = tls.connect({\n socket,\n host: input.address,\n ...(servername === undefined ? {} : { servername }),\n ...(input.alpn === undefined ? {} : { ALPNProtocols: [...input.alpn] }),\n secureContext: store.context,\n // The probe examines a certificate that is not valid, so the handshake does not stop at it. The probe then writes\n // no data. The HTTP probe sends its request only when the check of the certificate passed.\n // codeql[js/disabling-certificate-validation]\n rejectUnauthorized: false,\n });\n secure.on(\"error\", ignore);\n let done = false;\n const base = { ...(servername === undefined ? {} : { servername }), start };\n const finish = (phase: TlsPhase, ok: boolean): void => {\n if (done) return;\n done = true;\n if (ok) resolve({ secure, phase });\n else {\n secure.destroy();\n resolve({ phase });\n }\n };\n secure.once(\"secureConnect\", () => {\n const cipher = secure.getCipher().name;\n const protocol = secure.getProtocol() ?? undefined;\n const alpn = typeof secure.alpnProtocol === \"string\" ? secure.alpnProtocol : undefined;\n const certificate = certificateOf(secure, store);\n finish(\n {\n ...base,\n end: clock.now(),\n outcome: secure.authorized ? \"ok\" : \"cert-error\",\n ...(protocol === undefined ? {} : { protocol }),\n ...(cipher.length === 0 ? {} : { cipher }),\n ...(alpn === undefined ? {} : { alpn }),\n ...(certificate === undefined ? {} : { certificate }),\n },\n true,\n );\n });\n secure.once(\"error\", (error) => {\n const code = codeOf(error);\n finish({ ...base, end: clock.now(), outcome: handshakeOutcome(error, w.expired()), ...(code === undefined ? {} : { code }) }, false);\n });\n secure.once(\"close\", () => finish({ ...base, end: clock.now(), outcome: w.expired() ? \"timeout\" : \"closed\" }, false));\n w.guard(secure);\n });\n}\n\n/** This function gives the observation of a TLS phase. */\nexport function tlsObservation(address: string, port: number, phase: TlsPhase): Observation {\n const { start, end, ...rest } = phase;\n return observe(TLS_HANDSHAKE, target(address, port), { address, port, ...rest, ms: end - start }, { start, end });\n}\n\n/** The fields of a response head that an HTTP fact keeps. */\nexport const KEPT_FIELDS: readonly string[] = [\"server\", \"via\", \"location\", \"content-type\", \"content-length\", \"alt-svc\", \"x-cache\", \"cf-ray\"];\n\n/** The HTTP phase of a probe run. */\nexport interface HttpPhase {\n readonly method: \"GET\" | \"HEAD\";\n readonly host: string;\n readonly path: string;\n readonly start: number;\n /** The time of the head of the response. */\n readonly head?: number | undefined;\n readonly end: number;\n readonly outcome: HttpOutcome;\n readonly status?: number | undefined;\n readonly version?: string | undefined;\n readonly fields: Readonly<Record<string, string>>;\n readonly bytes: number;\n readonly truncated: boolean;\n readonly code?: string | undefined;\n}\n\n/** The input of an HTTP exchange. */\nexport interface ExchangeInput {\n readonly method: \"GET\" | \"HEAD\";\n readonly host: string;\n readonly path: string;\n readonly maxBytes: number;\n}\n\nfunction httpOutcome(thrown: unknown, expired: boolean): HttpOutcome {\n const end = endOf(thrown, expired);\n if (end !== undefined) return end;\n return (codeOf(thrown) ?? \"\").startsWith(\"HPE_\") ? \"protocol\" : \"error\";\n}\n\n/** This function sends one HTTP/1.1 request on a connected socket, and reads the response up to a limit of bytes. */\nexport function exchangePhase(socket: Socket, input: ExchangeInput, w: Watch, clock: Clock): Promise<HttpPhase> {\n return new Promise((resolve) => {\n let done = false;\n let head: number | undefined;\n let status: number | undefined;\n let version: string | undefined;\n let fields: Record<string, string> = {};\n let bytes = 0;\n let truncated = false;\n const start = clock.now();\n const finish = (outcome: HttpOutcome, code?: string): void => {\n if (done) return;\n done = true;\n resolve({\n method: input.method,\n host: input.host,\n path: input.path,\n start,\n ...(head === undefined ? {} : { head }),\n end: clock.now(),\n outcome,\n ...(status === undefined ? {} : { status }),\n ...(version === undefined ? {} : { version }),\n fields,\n bytes,\n truncated,\n ...(code === undefined ? {} : { code }),\n });\n socket.destroy();\n };\n const req = request({\n createConnection: () => socket,\n method: input.method,\n path: input.path,\n headers: { host: input.host, \"user-agent\": \"netdiag\", accept: \"*/*\", connection: \"close\" },\n });\n req.on(\"error\", (error) => finish(httpOutcome(error, w.expired()), codeOf(error)));\n req.on(\"close\", () => finish(w.expired() ? \"timeout\" : \"closed\"));\n req.on(\"response\", (res) => {\n head = clock.now();\n status = res.statusCode;\n version = res.httpVersion;\n fields = Object.fromEntries(\n KEPT_FIELDS.flatMap((name) => {\n const value = res.headers[name];\n return value === undefined ? [] : [[name, Array.isArray(value) ? value.join(\", \") : value]];\n }),\n );\n res.on(\"data\", (chunk: Buffer) => {\n bytes += chunk.length;\n if (bytes >= input.maxBytes) {\n truncated = true;\n finish(\"response\");\n }\n });\n res.on(\"end\", () => finish(\"response\"));\n res.on(\"error\", (error) => finish(httpOutcome(error, w.expired()), codeOf(error)));\n });\n req.end();\n });\n}\n\n/** This function gives the observation of an HTTP phase. */\nexport function httpObservation(address: string, port: number, scheme: \"http\" | \"https\", phase: HttpPhase): Observation {\n const { start, end, head, ...rest } = phase;\n const time: FactTime = { start, end };\n return observe(\n HTTP_RESPONSE,\n target(address, port),\n { address, port, scheme, ...rest, ...(head === undefined ? {} : { ttfbMs: head - start }), ms: end - start },\n time,\n );\n}\n","import { Resolver } from \"node:dns/promises\";\nimport { isIP } from \"node:net\";\nimport { observe, type Observation } from \"../fact.ts\";\nimport { DNS_ANSWER, TCP_CONNECT, TLS_HANDSHAKE, HTTP_RESPONSE, type DnsOutcome, type RecordType } from \"../kinds.ts\";\nimport { defineProbe, type Availability, type Probe } from \"../probe.ts\";\nimport { formatTarget, target } from \"../target.ts\";\nimport {\n connectPhase,\n dialOf,\n exchangePhase,\n handshakePhase,\n httpObservation,\n peerOf,\n tcpObservation,\n timeoutOf,\n tlsObservation,\n trustStore,\n watch,\n codeOf,\n type ConnectOptions,\n type HttpPhase,\n type TcpPhase,\n type TlsPhase,\n type TrustStore,\n} from \"./socket.ts\";\n\n// The native probes use only the `node:` modules. Each network failure is an outcome in the raw output, not an error.\n\nconst NATIVE: Availability = { available: true, version: process.versions.node };\n\nfunction native(): Promise<Availability> {\n return Promise.resolve(NATIVE);\n}\n\n/** The input of the DNS probe: one query for the addresses of a name, to one resolver. */\nexport interface DnsInput {\n readonly name: string;\n /** The record type. The default is `A`. */\n readonly record?: RecordType | undefined;\n /** The IP address of the resolver. */\n readonly resolver: string;\n /** The port of the resolver. The default is 53. */\n readonly port?: number | undefined;\n readonly timeoutMs?: number | undefined;\n}\n\n/** The raw output of the DNS probe. The times are readings of the clock of the runner. */\nexport interface DnsRaw {\n readonly name: string;\n readonly record: RecordType;\n readonly resolver: string;\n readonly start: number;\n readonly end: number;\n readonly outcome: DnsOutcome;\n readonly addresses: readonly string[];\n readonly ttl?: number | undefined;\n readonly code?: string | undefined;\n}\n\n/** This function maps an error code of a DNS query to its outcome. */\nexport function dnsOutcome(code: string | undefined): DnsOutcome {\n switch (code) {\n case \"ENOTFOUND\":\n return \"nxdomain\";\n case \"ENODATA\":\n return \"nodata\";\n case \"ESERVFAIL\":\n return \"servfail\";\n case \"EREFUSED\":\n return \"refused\";\n case \"ETIMEOUT\":\n return \"timeout\";\n case \"ECONNREFUSED\":\n return \"unreachable\";\n default:\n return \"error\";\n }\n}\n\n/**\n * The DNS probe sends one query for the addresses of a name to one resolver. Its peer is the resolver, not the name.\n * The query uses no search domain.\n */\nexport const DNS_PROBE: Probe<DnsInput, DnsRaw> = defineProbe<DnsInput, DnsRaw>({\n id: \"dns.query\",\n version: \"1\",\n summary: \"One DNS query for the A or AAAA records of a name, to one resolver.\",\n layer: \"dns\",\n mode: \"active\",\n kinds: [DNS_ANSWER],\n peers(input) {\n if (isIP(input.name) !== 0) throw new TypeError(`\"${input.name}\" is an address, not a name.`);\n return [peerOf(input.resolver, input.port ?? 53)];\n },\n available: native,\n async run(input, context) {\n const record = input.record ?? \"A\";\n const limit = timeoutOf(input.timeoutMs);\n const server = formatTarget(target(input.resolver, input.port ?? 53));\n const resolver = new Resolver({ timeout: limit, tries: 1 });\n resolver.setServers([server]);\n let expired = false;\n const stop = (): void => resolver.cancel();\n const cancel = context.clock.after(limit, () => {\n expired = true;\n stop();\n });\n context.signal.addEventListener(\"abort\", stop, { once: true });\n const start = context.clock.now();\n const base = { name: input.name, record, resolver: server, start };\n try {\n const answers = record === \"A\" ? await resolver.resolve4(input.name, { ttl: true }) : await resolver.resolve6(input.name, { ttl: true });\n const raw: DnsRaw = {\n ...base,\n end: context.clock.now(),\n outcome: \"ok\",\n addresses: answers.map((answer) => answer.address),\n ...(answers.length === 0 ? {} : { ttl: Math.min(...answers.map((answer) => answer.ttl)) }),\n };\n context.signal.throwIfAborted();\n return raw;\n } catch (thrown) {\n context.signal.throwIfAborted();\n const code = codeOf(thrown);\n return { ...base, end: context.clock.now(), outcome: expired ? \"timeout\" : dnsOutcome(code), addresses: [], ...(code === undefined ? {} : { code }) };\n } finally {\n cancel();\n context.signal.removeEventListener(\"abort\", stop);\n }\n },\n parse(raw) {\n const { start, end, ...rest } = raw;\n return [observe(DNS_ANSWER, target(raw.name), { ...rest, ms: end - start }, { start, end })];\n },\n});\n\n/** The input of the TCP probe: one connect to an IP address and a port. */\nexport interface TcpInput {\n readonly address: string;\n readonly port: number;\n readonly timeoutMs?: number | undefined;\n}\n\n/** The raw output of the TCP probe. */\nexport interface TcpRaw {\n readonly tcp: TcpPhase;\n}\n\n/** This function makes the TCP probe. It connects, and then it closes the connection. */\nexport function tcpProbe(options: ConnectOptions = {}): Probe<TcpInput, TcpRaw> {\n const dial = dialOf(options);\n return defineProbe<TcpInput, TcpRaw>({\n id: \"tcp.connect\",\n version: \"1\",\n summary: \"One TCP connect to an IP address and a port.\",\n layer: \"tcp\",\n mode: \"active\",\n kinds: [TCP_CONNECT],\n peers: (input) => [peerOf(input.address, input.port)],\n available: native,\n async run(input, context) {\n const w = watch(context, timeoutOf(input.timeoutMs));\n try {\n const { socket, phase } = await connectPhase(dial, input.address, input.port, w, context.clock);\n socket?.destroy();\n context.signal.throwIfAborted();\n return { tcp: phase };\n } finally {\n w.close();\n }\n },\n parse: (raw) => [tcpObservation(raw.tcp)],\n });\n}\n\n/** The TCP probe with the `connect` of `node:net`. */\nexport const TCP_PROBE: Probe<TcpInput, TcpRaw> = tcpProbe();\n\n/** The input of the TLS probe: a TCP connect and a TLS handshake. */\nexport interface TlsInput {\n readonly address: string;\n /** The port. The default is 443. */\n readonly port?: number | undefined;\n /** The name for SNI and for the check of the certificate. */\n readonly servername?: string | undefined;\n readonly alpn?: readonly string[] | undefined;\n readonly timeoutMs?: number | undefined;\n}\n\n/** The raw output of the TLS probe. It has no TLS phase when the TCP connect failed. */\nexport interface TlsRaw {\n readonly tcp: TcpPhase;\n readonly tls?: TlsPhase | undefined;\n}\n\n/** The options of the TLS and HTTP probes. */\nexport interface TlsProbeOptions extends ConnectOptions {\n /** More roots of trust, as PEM text: the roots that the user trusts, for example the root of a company. */\n readonly roots?: readonly string[] | undefined;\n /**\n * More public roots, as PEM text. A chain that ends at one of them gets the trust `public`. A lab gives its own\n * root here, so that its servers stand for servers on the internet.\n */\n readonly publicRoots?: readonly string[] | undefined;\n}\n\nfunction lazyStore(options: TlsProbeOptions): () => TrustStore {\n let store: TrustStore | undefined;\n return () => (store ??= trustStore(options.roots ?? [], options.publicRoots ?? []));\n}\n\n/**\n * This function makes the TLS probe. It measures the TCP connect and the TLS handshake apart. It examines the\n * certificate, and it does not refuse a certificate that is not valid.\n */\nexport function tlsProbe(options: TlsProbeOptions = {}): Probe<TlsInput, TlsRaw> {\n const dial = dialOf(options);\n const store = lazyStore(options);\n return defineProbe<TlsInput, TlsRaw>({\n id: \"tls.handshake\",\n version: \"1\",\n summary: \"One TCP connect and one TLS handshake, with the certificate of the server.\",\n layer: \"tls\",\n mode: \"active\",\n kinds: [TCP_CONNECT, TLS_HANDSHAKE],\n peers: (input) => [peerOf(input.address, input.port ?? 443)],\n available: native,\n async run(input, context) {\n const port = input.port ?? 443;\n const w = watch(context, timeoutOf(input.timeoutMs));\n try {\n const { socket, phase } = await connectPhase(dial, input.address, port, w, context.clock);\n if (socket === undefined) {\n context.signal.throwIfAborted();\n return { tcp: phase };\n }\n const handshake = await handshakePhase(socket, { address: input.address, servername: input.servername, alpn: input.alpn }, store(), w, context.clock);\n handshake.secure?.destroy();\n context.signal.throwIfAborted();\n return { tcp: phase, tls: handshake.phase };\n } finally {\n w.close();\n }\n },\n parse(raw) {\n const observations: Observation[] = [tcpObservation(raw.tcp)];\n if (raw.tls !== undefined) observations.push(tlsObservation(raw.tcp.address, raw.tcp.port, raw.tls));\n return observations;\n },\n });\n}\n\n/** The TLS probe with the public roots and the roots of the computer. */\nexport const TLS_PROBE: Probe<TlsInput, TlsRaw> = tlsProbe();\n\n/** The input of the HTTP probe: one HTTP/1.1 request over TCP, or over TLS for `https`. */\nexport interface HttpInput {\n readonly address: string;\n readonly scheme: \"http\" | \"https\";\n /** The port. The default is 80 for `http` and 443 for `https`. */\n readonly port?: number | undefined;\n /** The name of the host for the `Host` field and for SNI. The default is the address. */\n readonly host?: string | undefined;\n /** The path of the request. The default is `/`. */\n readonly path?: string | undefined;\n /** The method. The default is `GET`. */\n readonly method?: \"GET\" | \"HEAD\" | undefined;\n readonly timeoutMs?: number | undefined;\n /** The limit of the bytes of the body that the probe reads. The default is 65536. */\n readonly maxBytes?: number | undefined;\n}\n\n/** The raw output of the HTTP probe. */\nexport interface HttpRaw {\n readonly scheme: \"http\" | \"https\";\n readonly tcp: TcpPhase;\n readonly tls?: TlsPhase | undefined;\n readonly http?: HttpPhase | undefined;\n}\n\n/** The limit of the bytes of a body that the HTTP probe reads, when its input gives none: 64 KiB. */\nexport const DEFAULT_MAX_BYTES = 65536;\n\nfunction hostField(input: HttpInput, port: number): string {\n const host = input.host ?? (isIP(input.address) === 6 ? `[${input.address}]` : input.address);\n const standard = input.scheme === \"https\" ? 443 : 80;\n return port === standard ? host : `${host}:${port}`;\n}\n\n/**\n * This function makes the HTTP probe. It sends one request, follows no redirect, and reads the body up to a limit. It\n * gives the facts of each phase: TCP, TLS for `https`, and HTTP. For `https`, it sends the request only when the\n * certificate passes its check.\n */\nexport function httpProbe(options: TlsProbeOptions = {}): Probe<HttpInput, HttpRaw> {\n const dial = dialOf(options);\n const store = lazyStore(options);\n const portOf = (input: HttpInput): number => input.port ?? (input.scheme === \"https\" ? 443 : 80);\n return defineProbe<HttpInput, HttpRaw>({\n id: \"http.request\",\n version: \"1\",\n summary: \"One HTTP/1.1 request, with the time of each phase: TCP, TLS and the first byte.\",\n layer: \"http\",\n mode: \"active\",\n kinds: [TCP_CONNECT, TLS_HANDSHAKE, HTTP_RESPONSE],\n peers(input) {\n const path = input.path ?? \"/\";\n if (!path.startsWith(\"/\") || /[\\s]/.test(path)) throw new TypeError(`\"${path}\" is not a path that starts with \"/\" and has no space.`);\n return [peerOf(input.address, portOf(input))];\n },\n available: native,\n async run(input, context) {\n const port = portOf(input);\n const maxBytes = input.maxBytes ?? DEFAULT_MAX_BYTES;\n if (!Number.isInteger(maxBytes) || maxBytes < 1) throw new RangeError(`The limit of bytes ${maxBytes} is not a positive integer.`);\n const w = watch(context, timeoutOf(input.timeoutMs));\n try {\n const { socket, phase: tcp } = await connectPhase(dial, input.address, port, w, context.clock);\n if (socket === undefined) {\n context.signal.throwIfAborted();\n return { scheme: input.scheme, tcp };\n }\n let stream = socket;\n let secured: TlsPhase | undefined;\n if (input.scheme === \"https\") {\n const handshake = await handshakePhase(socket, { address: input.address, servername: input.host, alpn: [\"http/1.1\"] }, store(), w, context.clock);\n secured = handshake.phase;\n // A request on a session with a certificate that is not valid can send the path to an interceptor.\n if (handshake.secure === undefined || secured.outcome !== \"ok\") {\n handshake.secure?.destroy();\n context.signal.throwIfAborted();\n return { scheme: input.scheme, tcp, tls: secured };\n }\n stream = handshake.secure;\n }\n const http = await exchangePhase(stream, { method: input.method ?? \"GET\", host: hostField(input, port), path: input.path ?? \"/\", maxBytes }, w, context.clock);\n context.signal.throwIfAborted();\n return { scheme: input.scheme, tcp, ...(secured === undefined ? {} : { tls: secured }), http };\n } finally {\n w.close();\n }\n },\n parse(raw) {\n const { address, port } = raw.tcp;\n const observations: Observation[] = [tcpObservation(raw.tcp)];\n if (raw.tls !== undefined) observations.push(tlsObservation(address, port, raw.tls));\n if (raw.http !== undefined) observations.push(httpObservation(address, port, raw.scheme, raw.http));\n return observations;\n },\n });\n}\n\n/** The HTTP probe with the public roots and the roots of the computer. */\nexport const HTTP_PROBE: Probe<HttpInput, HttpRaw> = httpProbe();\n","import { addressKind, canonicalAddress } from \"./address/index.ts\";\nimport type { Evidence } from \"./diagnosis/model.ts\";\nimport { isFactOf, type Fact, type FactKind } from \"./fact.ts\";\nimport {\n DNS_ANSWER,\n HTTP_RESPONSE,\n ICMP_ECHO,\n PATH_REPORT,\n PATH_TRACE,\n TCP_CONNECT,\n TLS_HANDSHAKE,\n type ConnectOutcome,\n type DnsAnswer,\n type DnsOutcome,\n type EchoOutcome,\n type HandshakeOutcome,\n type HttpResponse,\n type IcmpEcho,\n type PathReport,\n type PathTrace,\n type TcpConnect,\n type TlsHandshake,\n type Trust,\n} from \"./kinds.ts\";\n\n// The readers change facts into evidence for the model. Each value that they give is a value of a feature of the\n// standard catalog, and a test holds it.\n\n/** A fact that gave no evidence, with the reason in plain words. */\nexport interface Skipped {\n readonly fact: string;\n readonly reason: string;\n}\n\n/** The evidence of a list of facts, and the facts that gave no evidence. */\nexport interface Reading {\n readonly evidence: readonly Evidence[];\n readonly skipped: readonly Skipped[];\n}\n\n/** The options of `readEvidence`. */\nexport interface ReadOptions {\n /** This function tells if a fact is about the target (`true`) or about a reference host (`false`). The default is `true`. */\n readonly primary?: ((fact: Fact) => boolean) | undefined;\n /**\n * The IP address of the default gateway. The ICMP echo facts of this address give the feature `gateway.reply`. The\n * ICMP echo facts of the other addresses give the feature `icmp.reply`.\n */\n readonly gateway?: string | undefined;\n}\n\n/** A bin of a time or a ratio. A number below `below` gets `value`. The last bin has no limit. */\nexport interface Bin {\n readonly below?: number | undefined;\n readonly value: string;\n}\n\n/** The bins of the feature `tcp.time`, in milliseconds. A connect of 900 ms or more sent its SYN again. */\nexport const TCP_TIME_BINS: readonly Bin[] = [{ below: 150, value: \"short\" }, { below: 900, value: \"long\" }, { value: \"retry\" }];\n\n/** The bins of the feature `tls.ratio`: the time of the TLS handshake divided by the time of the TCP connect. */\nexport const TLS_RATIO_BINS: readonly Bin[] = [{ below: 3, value: \"low\" }, { below: 8, value: \"mid\" }, { value: \"high\" }];\n\n/** The bins of the feature `http.ttfb`: the time to the first byte divided by the time of the TCP connect. */\nexport const HTTP_TTFB_BINS: readonly Bin[] = [{ below: 4, value: \"low\" }, { below: 20, value: \"mid\" }, { value: \"high\" }];\n\n/**\n * The smallest time of a TCP connect in a ratio, in milliseconds. On a local link, a ratio with a smaller time measures\n * only the cost of the cryptography.\n */\nexport const MIN_CONNECT_MS = 1;\n\n/** The value of the feature `dns.answer` for each outcome of a DNS query. `nodata` tells that the name exists. */\nexport const DNS_ANSWER_VALUES: Readonly<Partial<Record<DnsOutcome, string>>> = {\n ok: \"ok\",\n nodata: \"ok\",\n nxdomain: \"nxdomain\",\n servfail: \"servfail\",\n refused: \"refused\",\n timeout: \"timeout\",\n unreachable: \"unreachable\",\n};\n\n/** The value of the feature `tcp.connect` for each outcome of a TCP connect. */\nexport const TCP_CONNECT_VALUES: Readonly<Partial<Record<ConnectOutcome, string>>> = {\n connected: \"connected\",\n refused: \"refused\",\n timeout: \"timeout\",\n unreachable: \"unreachable\",\n reset: \"reset\",\n};\n\n/**\n * The value of the feature `tls.handshake` for each outcome of a TLS handshake. The catalog has no value for `not-tls`\n * and `closed` yet. The lab runs (ND-7) tell which faults cause them.\n */\nexport const TLS_HANDSHAKE_VALUES: Readonly<Partial<Record<HandshakeOutcome, string>>> = {\n ok: \"ok\",\n \"cert-error\": \"cert-error\",\n alert: \"alert\",\n timeout: \"timeout\",\n reset: \"reset\",\n};\n\n/** The value of the feature `tls.trust` for each trust. A chain that ends at no root of the store has no public root. */\nexport const TLS_TRUST_VALUES: Readonly<Record<Trust, string>> = { public: \"public\", private: \"private\", none: \"private\" };\n\n/** The value of the feature `icmp.reply` for each outcome of the ICMP echo requests to an address. */\nexport const ICMP_REPLY_VALUES: Readonly<Partial<Record<EchoOutcome, string>>> = { reply: \"reply\", none: \"none\", unreachable: \"unreachable\" };\n\n/**\n * The value of the feature `gateway.reply` for each outcome of the ICMP echo requests to the gateway. When this computer\n * tells that the gateway is unreachable, the gateway gives no reply.\n */\nexport const GATEWAY_REPLY_VALUES: Readonly<Partial<Record<EchoOutcome, string>>> = { reply: \"reply\", none: \"none\", unreachable: \"none\" };\n\n/**\n * The bins of the feature `path.loss`, on the share of the echo requests with a reply. A share of 1 has no loss. A share\n * of 0.95 or more has a loss of up to 5 %.\n */\nexport const PATH_LOSS_BINS: readonly Bin[] = [{ below: 0.95, value: \"high\" }, { below: 1, value: \"some\" }, { value: \"none\" }];\n\n/**\n * The smallest number of echo requests that gives the feature `path.loss`. With 20 requests, one lost request is a loss\n * of 5 %. With no reply, the feature `icmp.reply` tells the result, not `path.loss`.\n */\nexport const LOSS_MIN_SENT = 20;\n\n/** The suffixes of the names of local networks and of the special-use names (RFC 6761, RFC 6762 and RFC 8375). */\nexport const LOCAL_SUFFIXES: readonly string[] = [\"local\", \"localhost\", \"test\", \"invalid\", \"example\", \"home.arpa\", \"internal\", \"lan\", \"home\", \"corp\", \"intranet\", \"private\"];\n\n/** This function gives the value of the first bin that holds `x`. */\nexport function binOf(bins: readonly Bin[], x: number): string {\n for (const bin of bins) if (bin.below === undefined || x < bin.below) return bin.value;\n throw new RangeError(\"The last bin has a limit.\");\n}\n\n/** This function tells if a name is a local name: a name with one label, or a name with a local suffix. */\nexport function isLocalName(name: string): boolean {\n const lower = name.toLowerCase().replace(/\\.$/, \"\");\n if (!lower.includes(\".\")) return true;\n return LOCAL_SUFFIXES.some((suffix) => lower === suffix || lower.endsWith(`.${suffix}`));\n}\n\ninterface Slot {\n readonly feature: string;\n readonly primary: boolean;\n value: string;\n readonly facts: string[];\n}\n\ninterface Facts {\n readonly dns: Fact<DnsAnswer>[];\n readonly tcp: Fact<TcpConnect>[];\n readonly tls: Fact<TlsHandshake>[];\n readonly http: Fact<HttpResponse>[];\n readonly icmp: Fact<IcmpEcho>[];\n readonly report: Fact<PathReport>[];\n readonly trace: Fact<PathTrace>[];\n}\n\nconst READ_KINDS: readonly [keyof Facts, FactKind<unknown>][] = [\n [\"dns\", DNS_ANSWER],\n [\"tcp\", TCP_CONNECT],\n [\"tls\", TLS_HANDSHAKE],\n [\"http\", HTTP_RESPONSE],\n [\"icmp\", ICMP_ECHO],\n [\"report\", PATH_REPORT],\n [\"trace\", PATH_TRACE],\n];\n\n/**\n * This function changes facts into evidence for the model.\n *\n * - The reader gives one evidence for each feature, subject and role. When a subject has more facts, the last fact decides.\n * - For `dns.answer`, one answer `ok` of a name and a resolver decides. For `dns.address`, one private address decides.\n * - The order of the facts is the order of the runner. A ratio uses the last TCP connect before the handshake or the request.\n * - An ICMP echo fact gives `gateway.reply` for the gateway, and `icmp.reply` for each other address.\n * - An ICMP echo fact with replies to `LOSS_MIN_SENT` requests or more gives `path.loss`. A report of `mtr` gives it from\n * the last hop, when the address answered there.\n * - A trace of `traceroute` gives no evidence. The catalog has no feature for the hops of a path.\n */\nexport function readEvidence(facts: readonly Fact[], options: ReadOptions = {}): Reading {\n const primaryOf = options.primary ?? (() => true);\n const slots = new Map<string, Slot>();\n const skipped: Skipped[] = [];\n const sorted: Facts = { dns: [], tcp: [], tls: [], http: [], icmp: [], report: [], trace: [] };\n\n for (const fact of facts) {\n const entry = READ_KINDS.find(([, kind]) => kind.kind === fact.kind);\n if (entry === undefined) skipped.push({ fact: fact.id, reason: `netdiag has no reader for the kind ${fact.kind}.` });\n else if (!entry[1].check(fact.value)) skipped.push({ fact: fact.id, reason: `The value does not pass the check of the kind ${fact.kind}.` });\n else (sorted[entry[0]] as Fact[]).push(fact);\n }\n\n /** This function puts a value in the slot of a feature and a group. A `sticky` value stays when a later fact disagrees. */\n const note = (feature: string, group: string, fact: Fact, value: string, sticky?: string): void => {\n const primary = primaryOf(fact);\n const key = `${feature}\\u0000${primary ? 1 : 0}\\u0000${group}`;\n const slot = slots.get(key);\n if (slot === undefined) slots.set(key, { feature, primary, value, facts: [fact.id] });\n else {\n if (slot.value !== sticky) slot.value = value;\n slot.facts.push(fact.id);\n }\n };\n const skip = (fact: Fact, feature: string, outcome: string): void => {\n skipped.push({ fact: fact.id, reason: `The outcome \"${outcome}\" of ${fact.kind} has no value of the feature ${feature}.` });\n };\n\n for (const fact of sorted.dns) {\n const answer = fact.value;\n const group = `${answer.name.toLowerCase()}\\u0000${answer.resolver}`;\n const value = DNS_ANSWER_VALUES[answer.outcome];\n if (value === undefined) {\n skip(fact, \"dns.answer\", answer.outcome);\n continue;\n }\n note(\"dns.answer\", group, fact, value, \"ok\");\n if (answer.addresses.length > 0 && !isLocalName(answer.name)) {\n const kinds = answer.addresses.map(addressKind);\n note(\"dns.address\", group, fact, kinds.includes(\"private\") ? \"private\" : \"public\", \"private\");\n }\n }\n\n readFamily(sorted, primaryOf, note);\n\n const subject = (value: { readonly address: string; readonly port: number }): string => `${value.address}\\u0000${value.port}`;\n for (const fact of sorted.tcp) {\n const connect = fact.value;\n const value = TCP_CONNECT_VALUES[connect.outcome];\n if (value === undefined) {\n skip(fact, \"tcp.connect\", connect.outcome);\n continue;\n }\n note(\"tcp.connect\", subject(connect), fact, value);\n if (connect.outcome === \"connected\") note(\"tcp.time\", subject(connect), fact, binOf(TCP_TIME_BINS, connect.ms));\n }\n\n const connectBefore = (fact: Fact, value: { readonly address: string; readonly port: number }): Fact<TcpConnect> | undefined => {\n let best: Fact<TcpConnect> | undefined;\n for (const tcp of sorted.tcp) {\n const same = tcp.value.address === value.address && tcp.value.port === value.port && primaryOf(tcp) === primaryOf(fact);\n if (!same || tcp.value.outcome !== \"connected\" || tcp.time.end > fact.time.start) continue;\n if (best === undefined || tcp.time.end >= best.time.end) best = tcp;\n }\n return best;\n };\n const ratio = (ms: number, tcp: Fact<TcpConnect>): number => ms / Math.max(tcp.value.ms, MIN_CONNECT_MS);\n\n for (const fact of sorted.tls) {\n const handshake = fact.value;\n const group = `${subject(handshake)}\\u0000${handshake.servername ?? \"\"}`;\n const value = TLS_HANDSHAKE_VALUES[handshake.outcome];\n if (value === undefined) {\n skip(fact, \"tls.handshake\", handshake.outcome);\n continue;\n }\n note(\"tls.handshake\", group, fact, value);\n if (handshake.certificate !== undefined) note(\"tls.trust\", group, fact, TLS_TRUST_VALUES[handshake.certificate.trust]);\n const tcp = handshake.outcome === \"ok\" || handshake.outcome === \"cert-error\" ? connectBefore(fact, handshake) : undefined;\n if (tcp !== undefined) note(\"tls.ratio\", group, fact, binOf(TLS_RATIO_BINS, ratio(handshake.ms, tcp)));\n }\n\n for (const fact of sorted.http) {\n const response = fact.value;\n const group = [subject(response), response.scheme, response.host, response.method, response.path].join(\"\\u0000\");\n const status = statusValue(response);\n if (status === undefined) {\n skip(fact, \"http.status\", response.outcome === \"response\" ? `response ${response.status ?? \"\"}`.trim() : response.outcome);\n continue;\n }\n note(\"http.status\", group, fact, status);\n const tcp = response.ttfbMs === undefined ? undefined : connectBefore(fact, response);\n if (tcp !== undefined && response.ttfbMs !== undefined) note(\"http.ttfb\", group, fact, binOf(HTTP_TTFB_BINS, ratio(response.ttfbMs, tcp)));\n }\n\n const gateway = options.gateway === undefined ? undefined : canonicalAddress(options.gateway);\n for (const fact of sorted.icmp) {\n const echo = fact.value;\n const address = canonicalAddress(echo.address);\n const isGateway = address === gateway;\n const feature = isGateway ? \"gateway.reply\" : \"icmp.reply\";\n const value = (isGateway ? GATEWAY_REPLY_VALUES : ICMP_REPLY_VALUES)[echo.outcome];\n if (value === undefined) {\n skip(fact, feature, echo.outcome);\n continue;\n }\n note(feature, address, fact, value);\n }\n\n // The feature path.loss comes from ping and from mtr. The facts of both kinds go in the order of the runner.\n const lossFacts = new Set<Fact>([...sorted.icmp, ...sorted.report]);\n for (const fact of facts) {\n if (!lossFacts.has(fact)) continue;\n const loss = lossOf(fact);\n if (typeof loss === \"string\") skipped.push({ fact: fact.id, reason: loss });\n else if (loss !== undefined) note(\"path.loss\", loss.address, fact, binOf(PATH_LOSS_BINS, loss.share));\n }\n\n for (const fact of sorted.trace) {\n skipped.push({ fact: fact.id, reason: \"The catalog has no feature for the hops of a path. The lab runs fit such a feature later.\" });\n }\n\n const evidence = [...slots.values()].map(({ feature, value, primary, facts: ids }) => ({ feature, value, primary, facts: ids }));\n return { evidence, skipped };\n}\n\n/**\n * This function gives the share of the probes with an answer, for the feature `path.loss`. It gives a reason when a report\n * of `mtr` gives no evidence, and `undefined` when an echo fact gives no share.\n */\nfunction lossOf(fact: Fact): { readonly address: string; readonly share: number } | string | undefined {\n if (isFactOf(ICMP_ECHO, fact)) {\n const echo = fact.value;\n return echo.received > 0 && echo.sent >= LOSS_MIN_SENT ? { address: canonicalAddress(echo.address), share: echo.received / echo.sent } : undefined;\n }\n if (!isFactOf(PATH_REPORT, fact)) return undefined;\n const report = fact.value;\n const last = report.hops.at(-1);\n if (report.outcome !== \"reached\" || last === undefined) return `The outcome \"${report.outcome}\" of ${fact.kind} has no value of the feature path.loss.`;\n if (last.sent < LOSS_MIN_SENT) return `The last hop of the report has ${last.sent} probes. The feature path.loss needs ${LOSS_MIN_SENT} probes or more.`;\n return { address: canonicalAddress(report.address), share: last.received / last.sent };\n}\n\nfunction statusValue(response: HttpResponse): string | undefined {\n if (response.outcome === \"error\") return undefined;\n if (response.outcome !== \"response\") return \"none\";\n const status = response.status ?? 0;\n if (status >= 200 && status < 400) return \"ok\";\n if (status >= 400 && status < 500) return \"client-error\";\n if (status >= 500 && status < 600) return \"server-error\";\n return undefined;\n}\n\n/**\n * This function gives the feature `ip.family` for a name with IPv4 and IPv6 addresses. It needs a TCP connect to an\n * address of each family.\n */\nfunction readFamily(sorted: Facts, primaryOf: (fact: Fact) => boolean, note: (feature: string, group: string, fact: Fact, value: string) => void): void {\n const names = new Map<string, { v4: Set<string>; v6: Set<string>; facts: Fact[]; primary: boolean }>();\n for (const fact of sorted.dns) {\n if (fact.value.outcome !== \"ok\") continue;\n const key = `${primaryOf(fact) ? 1 : 0}\\u0000${fact.value.name.toLowerCase()}`;\n const entry = names.get(key) ?? { v4: new Set<string>(), v6: new Set<string>(), facts: [], primary: primaryOf(fact) };\n for (const address of fact.value.addresses) (fact.value.record === \"A\" ? entry.v4 : entry.v6).add(address);\n entry.facts.push(fact);\n names.set(key, entry);\n }\n for (const [key, entry] of names) {\n if (entry.v4.size === 0 || entry.v6.size === 0) continue;\n const tried = sorted.tcp.filter((tcp) => primaryOf(tcp) === entry.primary && (entry.v4.has(tcp.value.address) || entry.v6.has(tcp.value.address)));\n const works = (family: Set<string>): boolean | undefined => {\n const connects = tried.filter((tcp) => family.has(tcp.value.address));\n return connects.length === 0 ? undefined : connects.some((tcp) => tcp.value.outcome === \"connected\");\n };\n const v4 = works(entry.v4);\n const v6 = works(entry.v6);\n if (v4 === undefined || v6 === undefined || (!v4 && !v6)) continue;\n const value = v4 && v6 ? \"both\" : v4 ? \"ipv4-only\" : \"ipv6-only\";\n const evidenceFacts = [...entry.facts, ...tried];\n const [first, ...rest] = evidenceFacts;\n if (first === undefined) continue;\n note(\"ip.family\", key, first, value);\n for (const fact of rest) note(\"ip.family\", key, fact, value);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
import { err, ok } from "@mark1russell7/optional";
|
|
3
|
+
// src/target.ts
|
|
4
|
+
/** The ports of the URL schemes that a target can have. */
|
|
5
|
+
const SCHEME_PORTS = /* @__PURE__ */ new Map([
|
|
6
|
+
["http:", 80],
|
|
7
|
+
["https:", 443],
|
|
8
|
+
["ws:", 80],
|
|
9
|
+
["wss:", 443],
|
|
10
|
+
["dns:", 53],
|
|
11
|
+
["tls:", 443]
|
|
12
|
+
]);
|
|
13
|
+
/** This function gives the kind of a host in its canonical form. */
|
|
14
|
+
function hostKind(host) {
|
|
15
|
+
const family = isIP(host);
|
|
16
|
+
if (family === 4) return "ipv4";
|
|
17
|
+
if (family === 6) return "ipv6";
|
|
18
|
+
return "name";
|
|
19
|
+
}
|
|
20
|
+
/** This function gives the canonical form of a host: lower case, with no final dot and no brackets. */
|
|
21
|
+
function canonicalHost(host) {
|
|
22
|
+
let text = host.trim().toLowerCase();
|
|
23
|
+
if (text.startsWith("[") && text.endsWith("]")) text = text.slice(1, -1);
|
|
24
|
+
if (text.endsWith(".")) text = text.slice(0, -1);
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
/** This function makes a target from a host and an optional port. */
|
|
28
|
+
function target(host, port) {
|
|
29
|
+
const canonical = canonicalHost(host);
|
|
30
|
+
return port === void 0 ? {
|
|
31
|
+
host: canonical,
|
|
32
|
+
kind: hostKind(canonical)
|
|
33
|
+
} : {
|
|
34
|
+
host: canonical,
|
|
35
|
+
kind: hostKind(canonical),
|
|
36
|
+
port
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** This function gives the text of a target: `host`, `host:port`, or `[address]:port` for an IPv6 address. */
|
|
40
|
+
function formatTarget(t) {
|
|
41
|
+
const host = t.kind === "ipv6" && t.port !== void 0 ? `[${t.host}]` : t.host;
|
|
42
|
+
return t.port === void 0 ? host : `${host}:${t.port}`;
|
|
43
|
+
}
|
|
44
|
+
/** This function tells if two targets have the same host and the same port. */
|
|
45
|
+
function sameTarget(a, b) {
|
|
46
|
+
return a.host === b.host && a.port === b.port;
|
|
47
|
+
}
|
|
48
|
+
const LABEL = /^(?!-)[a-z0-9-]{1,63}(?<!-)$/;
|
|
49
|
+
function validName(host) {
|
|
50
|
+
if (host.length === 0 || host.length > 253) return false;
|
|
51
|
+
return host.split(".").every((label) => LABEL.test(label) || label === "_" || /^_[a-z0-9-]{1,62}$/.test(label));
|
|
52
|
+
}
|
|
53
|
+
function portOf(text) {
|
|
54
|
+
if (!/^\d{1,5}$/.test(text)) return void 0;
|
|
55
|
+
const port = Number(text);
|
|
56
|
+
return port >= 1 && port <= 65535 ? port : void 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* This function reads a target from the text that a user gives.
|
|
60
|
+
*
|
|
61
|
+
* It accepts a URL (`https://example.com/a`), a host (`example.com`) and a host with a port (`example.com:8443`).
|
|
62
|
+
* It also accepts an IPv4 address, and an IPv6 address in brackets with a port (`[2001:db8::1]:443`).
|
|
63
|
+
* A URL with a known scheme and no port gets the port of its scheme.
|
|
64
|
+
*/
|
|
65
|
+
function parseTarget(text) {
|
|
66
|
+
const input = text.trim();
|
|
67
|
+
if (input.length === 0) return err("The target is empty.");
|
|
68
|
+
if (input.includes("://")) {
|
|
69
|
+
let url;
|
|
70
|
+
try {
|
|
71
|
+
url = new URL(input);
|
|
72
|
+
} catch {
|
|
73
|
+
return err(`"${input}" is not a valid URL.`);
|
|
74
|
+
}
|
|
75
|
+
const host = canonicalHost(url.hostname);
|
|
76
|
+
if (host.length === 0) return err(`The URL "${input}" has no host.`);
|
|
77
|
+
return checked(host, url.port === "" ? SCHEME_PORTS.get(url.protocol) : Number(url.port), input);
|
|
78
|
+
}
|
|
79
|
+
if (isIP(input) === 6) return checked(canonicalHost(input), void 0, input);
|
|
80
|
+
const bracket = /^\[([^\]]+)\](?::(.*))?$/.exec(input);
|
|
81
|
+
if (bracket !== null) {
|
|
82
|
+
const host = bracket[1] ?? "";
|
|
83
|
+
if (isIP(host) !== 6) return err(`"${host}" is not an IPv6 address.`);
|
|
84
|
+
if (bracket[2] === void 0) return checked(canonicalHost(host), void 0, input);
|
|
85
|
+
const port = portOf(bracket[2]);
|
|
86
|
+
return port === void 0 ? err(`"${bracket[2]}" is not a port from 1 to 65535.`) : checked(canonicalHost(host), port, input);
|
|
87
|
+
}
|
|
88
|
+
const colon = input.lastIndexOf(":");
|
|
89
|
+
if (colon === -1) return checked(canonicalHost(input), void 0, input);
|
|
90
|
+
const port = portOf(input.slice(colon + 1));
|
|
91
|
+
if (port === void 0) return err(`"${input.slice(colon + 1)}" is not a port from 1 to 65535.`);
|
|
92
|
+
return checked(canonicalHost(input.slice(0, colon)), port, input);
|
|
93
|
+
}
|
|
94
|
+
function checked(host, port, input) {
|
|
95
|
+
const kind = hostKind(host);
|
|
96
|
+
if (kind === "name" && !validName(host)) return err(`"${input}" has no valid host name.`);
|
|
97
|
+
return ok(port === void 0 ? {
|
|
98
|
+
host,
|
|
99
|
+
kind
|
|
100
|
+
} : {
|
|
101
|
+
host,
|
|
102
|
+
kind,
|
|
103
|
+
port
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// src/fact.ts
|
|
107
|
+
const KIND_NAME = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
|
|
108
|
+
/** This function makes a fact kind. The name has lower-case words with dots, for example `tls.handshake`. */
|
|
109
|
+
function defineFactKind(spec) {
|
|
110
|
+
if (!KIND_NAME.test(spec.kind)) throw new RangeError(`"${spec.kind}" is not a valid name of a fact kind`);
|
|
111
|
+
return Object.freeze({ ...spec });
|
|
112
|
+
}
|
|
113
|
+
/** This function tells if a fact is of a kind, and if its value passes the check of the kind. */
|
|
114
|
+
function isFactOf(kind, fact) {
|
|
115
|
+
return fact.kind === kind.kind && fact.layer === kind.layer && kind.check(fact.value);
|
|
116
|
+
}
|
|
117
|
+
/** This function gives the facts of one kind, in their order. */
|
|
118
|
+
function factsOf(kind, facts) {
|
|
119
|
+
return facts.filter((fact) => isFactOf(kind, fact));
|
|
120
|
+
}
|
|
121
|
+
/** This function makes an observation with a type check of its value against its kind. */
|
|
122
|
+
function observe(kind, subject, value, time) {
|
|
123
|
+
return time === void 0 ? {
|
|
124
|
+
kind,
|
|
125
|
+
subject,
|
|
126
|
+
value
|
|
127
|
+
} : {
|
|
128
|
+
kind,
|
|
129
|
+
subject,
|
|
130
|
+
value,
|
|
131
|
+
time
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// src/probe.ts
|
|
135
|
+
/** This function makes a probe, and examines its fields. */
|
|
136
|
+
function defineProbe(probe) {
|
|
137
|
+
if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(probe.id)) throw new RangeError(`"${probe.id}" is not a valid probe id`);
|
|
138
|
+
if (probe.kinds.length === 0) throw new RangeError(`The probe "${probe.id}" gives no kind of fact`);
|
|
139
|
+
return probe;
|
|
140
|
+
}
|
|
141
|
+
// src/kinds.ts
|
|
142
|
+
function isRecord(value) {
|
|
143
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
144
|
+
}
|
|
145
|
+
function isText(value) {
|
|
146
|
+
return typeof value === "string" && value.length > 0;
|
|
147
|
+
}
|
|
148
|
+
function isDuration(value) {
|
|
149
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
150
|
+
}
|
|
151
|
+
function isPort(value) {
|
|
152
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
|
|
153
|
+
}
|
|
154
|
+
function isCount(value) {
|
|
155
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0;
|
|
156
|
+
}
|
|
157
|
+
function isOneOf(values, value) {
|
|
158
|
+
return typeof value === "string" && values.includes(value);
|
|
159
|
+
}
|
|
160
|
+
function optional(value, check) {
|
|
161
|
+
return value === void 0 || check(value);
|
|
162
|
+
}
|
|
163
|
+
function isTextList(value) {
|
|
164
|
+
return Array.isArray(value) && value.every(isText);
|
|
165
|
+
}
|
|
166
|
+
/** The outcomes of a DNS query. `nodata` tells that the name exists, but it has no record of the type. */
|
|
167
|
+
const DNS_OUTCOMES = [
|
|
168
|
+
"ok",
|
|
169
|
+
"nodata",
|
|
170
|
+
"nxdomain",
|
|
171
|
+
"servfail",
|
|
172
|
+
"refused",
|
|
173
|
+
"timeout",
|
|
174
|
+
"unreachable",
|
|
175
|
+
"error"
|
|
176
|
+
];
|
|
177
|
+
/** The record types of a DNS query for addresses. */
|
|
178
|
+
const RECORD_TYPES = ["A", "AAAA"];
|
|
179
|
+
/** The fact kind `dns.answer`. */
|
|
180
|
+
const DNS_ANSWER = defineFactKind({
|
|
181
|
+
kind: "dns.answer",
|
|
182
|
+
layer: "dns",
|
|
183
|
+
summary: "The answer of a DNS resolver to one query for the addresses of a name.",
|
|
184
|
+
check: (value) => isRecord(value) && isText(value["name"]) && isOneOf(RECORD_TYPES, value["record"]) && isText(value["resolver"]) && isOneOf(DNS_OUTCOMES, value["outcome"]) && isTextList(value["addresses"]) && (value["outcome"] !== "ok" || value["addresses"].length > 0) && optional(value["ttl"], isDuration) && isDuration(value["ms"]) && optional(value["code"], isText)
|
|
185
|
+
});
|
|
186
|
+
/** The outcomes of a TCP connect. */
|
|
187
|
+
const CONNECT_OUTCOMES = [
|
|
188
|
+
"connected",
|
|
189
|
+
"refused",
|
|
190
|
+
"timeout",
|
|
191
|
+
"unreachable",
|
|
192
|
+
"reset",
|
|
193
|
+
"error"
|
|
194
|
+
];
|
|
195
|
+
/** The fact kind `tcp.connect`. */
|
|
196
|
+
const TCP_CONNECT = defineFactKind({
|
|
197
|
+
kind: "tcp.connect",
|
|
198
|
+
layer: "tcp",
|
|
199
|
+
summary: "The outcome and the time of one TCP connect to an address and a port.",
|
|
200
|
+
check: (value) => isRecord(value) && isText(value["address"]) && isPort(value["port"]) && (value["family"] === 4 || value["family"] === 6) && isOneOf(CONNECT_OUTCOMES, value["outcome"]) && isDuration(value["ms"]) && optional(value["code"], isText)
|
|
201
|
+
});
|
|
202
|
+
/**
|
|
203
|
+
* The outcomes of a TLS handshake.
|
|
204
|
+
*
|
|
205
|
+
* - `cert-error`: the handshake ended, but the certificate is not valid for the name.
|
|
206
|
+
* - `alert`: the client and the server stopped the handshake, for example because they have no version in common.
|
|
207
|
+
* - `not-tls`: the server does not speak TLS on the port.
|
|
208
|
+
* - `closed`: the server closed the connection before the end of the handshake.
|
|
209
|
+
*/
|
|
210
|
+
const HANDSHAKE_OUTCOMES = [
|
|
211
|
+
"ok",
|
|
212
|
+
"cert-error",
|
|
213
|
+
"alert",
|
|
214
|
+
"not-tls",
|
|
215
|
+
"timeout",
|
|
216
|
+
"reset",
|
|
217
|
+
"closed",
|
|
218
|
+
"error"
|
|
219
|
+
];
|
|
220
|
+
/** The roots of trust that a certificate chain ends at: a public root, a root of the computer or the user, or none. */
|
|
221
|
+
const TRUSTS = [
|
|
222
|
+
"public",
|
|
223
|
+
"private",
|
|
224
|
+
"none"
|
|
225
|
+
];
|
|
226
|
+
/** The problems of a certificate. */
|
|
227
|
+
const CERT_PROBLEMS = [
|
|
228
|
+
"expired",
|
|
229
|
+
"not-yet-valid",
|
|
230
|
+
"name",
|
|
231
|
+
"untrusted",
|
|
232
|
+
"revoked",
|
|
233
|
+
"other"
|
|
234
|
+
];
|
|
235
|
+
function isCertificate(value) {
|
|
236
|
+
return isRecord(value) && typeof value["subject"] === "string" && typeof value["issuer"] === "string" && isTextList(value["names"]) && isText(value["validFrom"]) && isText(value["validTo"]) && isText(value["fingerprint256"]) && typeof value["selfSigned"] === "boolean" && typeof value["chain"] === "number" && Number.isInteger(value["chain"]) && value["chain"] >= 1 && isOneOf(TRUSTS, value["trust"]) && optional(value["problem"], (problem) => isOneOf(CERT_PROBLEMS, problem));
|
|
237
|
+
}
|
|
238
|
+
/** The fact kind `tls.handshake`. */
|
|
239
|
+
const TLS_HANDSHAKE = defineFactKind({
|
|
240
|
+
kind: "tls.handshake",
|
|
241
|
+
layer: "tls",
|
|
242
|
+
summary: "The outcome and the time of one TLS handshake, and the certificate of the server.",
|
|
243
|
+
check: (value) => isRecord(value) && isText(value["address"]) && isPort(value["port"]) && optional(value["servername"], isText) && isOneOf(HANDSHAKE_OUTCOMES, value["outcome"]) && isDuration(value["ms"]) && optional(value["protocol"], isText) && optional(value["cipher"], isText) && optional(value["alpn"], isText) && optional(value["code"], isText) && optional(value["certificate"], isCertificate)
|
|
244
|
+
});
|
|
245
|
+
/**
|
|
246
|
+
* The outcomes of an HTTP request.
|
|
247
|
+
*
|
|
248
|
+
* - `response`: the server sent a response. Its status can be an error.
|
|
249
|
+
* - `protocol`: the server sent bytes that are not HTTP.
|
|
250
|
+
* - `closed`: the server closed the connection before the response.
|
|
251
|
+
*/
|
|
252
|
+
const HTTP_OUTCOMES = [
|
|
253
|
+
"response",
|
|
254
|
+
"protocol",
|
|
255
|
+
"timeout",
|
|
256
|
+
"reset",
|
|
257
|
+
"closed",
|
|
258
|
+
"error"
|
|
259
|
+
];
|
|
260
|
+
/** The fact kind `http.response`. */
|
|
261
|
+
const HTTP_RESPONSE = defineFactKind({
|
|
262
|
+
kind: "http.response",
|
|
263
|
+
layer: "http",
|
|
264
|
+
summary: "The outcome and the times of one HTTP request: the status, some fields of the head and the time to the first byte.",
|
|
265
|
+
check: (value) => isRecord(value) && isText(value["address"]) && isPort(value["port"]) && (value["scheme"] === "http" || value["scheme"] === "https") && (value["method"] === "GET" || value["method"] === "HEAD") && isText(value["host"]) && isText(value["path"]) && isOneOf(HTTP_OUTCOMES, value["outcome"]) && (value["outcome"] !== "response" || typeof value["status"] === "number" && Number.isInteger(value["status"])) && optional(value["status"], (status) => typeof status === "number" && status >= 100 && status <= 999) && optional(value["version"], isText) && isRecord(value["fields"]) && Object.values(value["fields"]).every((field) => typeof field === "string") && typeof value["bytes"] === "number" && Number.isInteger(value["bytes"]) && value["bytes"] >= 0 && typeof value["truncated"] === "boolean" && optional(value["ttfbMs"], isDuration) && isDuration(value["ms"]) && optional(value["code"], isText)
|
|
266
|
+
});
|
|
267
|
+
/** The fact kinds of the native probes. */
|
|
268
|
+
const NATIVE_KINDS = [
|
|
269
|
+
DNS_ANSWER,
|
|
270
|
+
TCP_CONNECT,
|
|
271
|
+
TLS_HANDSHAKE,
|
|
272
|
+
HTTP_RESPONSE
|
|
273
|
+
];
|
|
274
|
+
/**
|
|
275
|
+
* The outcomes of the ICMP echo requests to one address.
|
|
276
|
+
*
|
|
277
|
+
* - `reply`: one or more echo replies came back.
|
|
278
|
+
* - `none`: no reply came back in the time limit.
|
|
279
|
+
* - `unreachable`: no echo reply came back, and a router or this computer told that the address is unreachable.
|
|
280
|
+
* - `error`: the tool did not start, or netdiag cannot read its output.
|
|
281
|
+
*/
|
|
282
|
+
const ECHO_OUTCOMES = [
|
|
283
|
+
"reply",
|
|
284
|
+
"none",
|
|
285
|
+
"unreachable",
|
|
286
|
+
"error"
|
|
287
|
+
];
|
|
288
|
+
/** The fact kind `icmp.echo`. */
|
|
289
|
+
const ICMP_ECHO = defineFactKind({
|
|
290
|
+
kind: "icmp.echo",
|
|
291
|
+
layer: "ip",
|
|
292
|
+
summary: "The replies to the ICMP echo requests to one address, their round-trip times and the messages of an unreachable address.",
|
|
293
|
+
check: (value) => isRecord(value) && isText(value["address"]) && isOneOf(ECHO_OUTCOMES, value["outcome"]) && isCount(value["sent"]) && isCount(value["received"]) && value["received"] <= value["sent"] && Array.isArray(value["rttMs"]) && value["rttMs"].every(isDuration) && value["rttMs"].length <= value["received"] && value["outcome"] === "reply" === value["received"] > 0 && optional(value["ttl"], isCount) && optional(value["from"], isText) && optional(value["detail"], isText)
|
|
294
|
+
});
|
|
295
|
+
/**
|
|
296
|
+
* The outcomes of a trace of the path to an address.
|
|
297
|
+
*
|
|
298
|
+
* - `reached`: the address answered at the last hop.
|
|
299
|
+
* - `stopped`: the last hop is not the address, and no router told that the address is unreachable.
|
|
300
|
+
* - `unreachable`: a router or this computer told that the address is unreachable.
|
|
301
|
+
* - `error`: the tool failed, or netdiag cannot read its output.
|
|
302
|
+
*/
|
|
303
|
+
const TRACE_OUTCOMES = [
|
|
304
|
+
"reached",
|
|
305
|
+
"stopped",
|
|
306
|
+
"unreachable",
|
|
307
|
+
"error"
|
|
308
|
+
];
|
|
309
|
+
function isTraceHop(value) {
|
|
310
|
+
return isRecord(value) && isCount(value["ttl"]) && value["ttl"] >= 1 && isTextList(value["addresses"]) && isCount(value["sent"]) && isCount(value["received"]) && value["received"] <= value["sent"] && value["received"] > 0 === value["addresses"].length > 0 && Array.isArray(value["rttMs"]) && value["rttMs"].every(isDuration) && value["rttMs"].length <= value["received"] && optional(value["unreachable"], isText);
|
|
311
|
+
}
|
|
312
|
+
/** This function tells if the TTL of each hop is larger than the TTL of the hop before it, and not larger than `maxHops`. */
|
|
313
|
+
function inOrder(hops, maxHops) {
|
|
314
|
+
return hops.every((hop, index) => hop.ttl <= maxHops && (index === 0 || hop.ttl > (hops[index - 1]?.ttl ?? 0)));
|
|
315
|
+
}
|
|
316
|
+
/** The fact kind `path.trace`. */
|
|
317
|
+
const PATH_TRACE = defineFactKind({
|
|
318
|
+
kind: "path.trace",
|
|
319
|
+
layer: "path",
|
|
320
|
+
summary: "The hops of the path to one address: the routers that answered each TTL, their round-trip times and the messages of an unreachable address.",
|
|
321
|
+
check: (value) => isRecord(value) && isText(value["address"]) && isOneOf(TRACE_OUTCOMES, value["outcome"]) && isCount(value["maxHops"]) && Array.isArray(value["hops"]) && value["hops"].every(isTraceHop) && inOrder(value["hops"], value["maxHops"]) && (value["outcome"] !== "reached" || value["hops"].length > 0) && optional(value["detail"], isText)
|
|
322
|
+
});
|
|
323
|
+
function isReportHop(value) {
|
|
324
|
+
return isRecord(value) && isCount(value["ttl"]) && value["ttl"] >= 1 && optional(value["address"], isText) && isCount(value["sent"]) && isCount(value["received"]) && value["received"] <= value["sent"] && (value["received"] === 0 || value["address"] !== void 0) && optional(value["bestMs"], isDuration) && optional(value["meanMs"], isDuration) && optional(value["worstMs"], isDuration);
|
|
325
|
+
}
|
|
326
|
+
/** The fact kind `path.report`. */
|
|
327
|
+
const PATH_REPORT = defineFactKind({
|
|
328
|
+
kind: "path.report",
|
|
329
|
+
layer: "path",
|
|
330
|
+
summary: "The hops of the path to one address over many cycles of probes: the address, the loss and the round-trip times of each hop.",
|
|
331
|
+
check: (value) => isRecord(value) && isText(value["address"]) && isOneOf(TRACE_OUTCOMES, value["outcome"]) && value["outcome"] !== "unreachable" && isCount(value["cycles"]) && Array.isArray(value["hops"]) && value["hops"].every(isReportHop) && inOrder(value["hops"], Number.MAX_SAFE_INTEGER) && (value["outcome"] !== "reached" || value["hops"].length > 0) && optional(value["detail"], isText)
|
|
332
|
+
});
|
|
333
|
+
/** The fact kinds of the adapters of outside tools (`@mark1russell7/netdiag/tools`). */
|
|
334
|
+
const TOOL_KINDS = [
|
|
335
|
+
ICMP_ECHO,
|
|
336
|
+
PATH_TRACE,
|
|
337
|
+
PATH_REPORT
|
|
338
|
+
];
|
|
339
|
+
export { observe as C, parseTarget as D, hostKind as E, sameTarget as O, isFactOf as S, formatTarget as T, TRACE_OUTCOMES as _, ECHO_OUTCOMES as a, defineFactKind as b, HTTP_RESPONSE as c, PATH_REPORT as d, PATH_TRACE as f, TOOL_KINDS as g, TLS_HANDSHAKE as h, DNS_OUTCOMES as i, target as k, ICMP_ECHO as l, TCP_CONNECT as m, CONNECT_OUTCOMES as n, HANDSHAKE_OUTCOMES as o, RECORD_TYPES as p, DNS_ANSWER as r, HTTP_OUTCOMES as s, CERT_PROBLEMS as t, NATIVE_KINDS as u, TRUSTS as v, canonicalHost as w, factsOf as x, defineProbe as y };
|
|
340
|
+
|
|
341
|
+
//# sourceMappingURL=kinds-CQv769lK.js.map
|