@mark1russell7/netdiag 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +92 -2
- package/build/diagnosis-BTa-Dfwl.js +820 -0
- package/build/diagnosis-BTa-Dfwl.js.map +1 -0
- package/build/diagnosis.d.ts +7 -0
- package/build/diagnosis.js +1 -0
- package/build/index-CP1hzELI.d.ts +198 -0
- package/build/index-CP1hzELI.d.ts.map +1 -0
- package/build/index.d.ts +287 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +436 -0
- package/build/index.js.map +1 -0
- package/package.json +69 -3
- package/src/diagnosis/catalog.ts +357 -0
- package/src/diagnosis/index.ts +34 -0
- package/src/diagnosis/model.ts +322 -0
- package/src/fact.ts +87 -0
- package/src/index.ts +33 -0
- package/src/layer.ts +31 -0
- package/src/probe.ts +61 -0
- package/src/run.ts +163 -0
- package/src/scope.ts +171 -0
- package/src/target.ts +116 -0
package/build/index.js
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { _ as layerRank, a as FLOOR, c as STRENGTHS, d as diagnose, f as expand, g as isLayer, h as LAYERS, i as STANDARD_FEATURES, l as UNKNOWN, m as predictive, n as STANDARD_CATALOG_SPEC, o as NONE, p as explain, r as STANDARD_FAULTS, s as PARTS, t as STANDARD_CATALOG, u as compileCatalog } from "./diagnosis-BTa-Dfwl.js";
|
|
2
|
+
import { BlockList, isIP } from "node:net";
|
|
3
|
+
import { err, ok } from "@mark1russell7/optional";
|
|
4
|
+
// src/target.ts
|
|
5
|
+
/** The ports of the URL schemes that a target can have. */
|
|
6
|
+
const SCHEME_PORTS = /* @__PURE__ */ new Map([
|
|
7
|
+
["http:", 80],
|
|
8
|
+
["https:", 443],
|
|
9
|
+
["ws:", 80],
|
|
10
|
+
["wss:", 443],
|
|
11
|
+
["dns:", 53],
|
|
12
|
+
["tls:", 443]
|
|
13
|
+
]);
|
|
14
|
+
/** This function gives the kind of a host in its canonical form. */
|
|
15
|
+
function hostKind(host) {
|
|
16
|
+
const family = isIP(host);
|
|
17
|
+
if (family === 4) return "ipv4";
|
|
18
|
+
if (family === 6) return "ipv6";
|
|
19
|
+
return "name";
|
|
20
|
+
}
|
|
21
|
+
/** This function gives the canonical form of a host: lower case, with no final dot and no brackets. */
|
|
22
|
+
function canonicalHost(host) {
|
|
23
|
+
let text = host.trim().toLowerCase();
|
|
24
|
+
if (text.startsWith("[") && text.endsWith("]")) text = text.slice(1, -1);
|
|
25
|
+
if (text.endsWith(".")) text = text.slice(0, -1);
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
/** This function makes a target from a host and an optional port. */
|
|
29
|
+
function target(host, port) {
|
|
30
|
+
const canonical = canonicalHost(host);
|
|
31
|
+
return port === void 0 ? {
|
|
32
|
+
host: canonical,
|
|
33
|
+
kind: hostKind(canonical)
|
|
34
|
+
} : {
|
|
35
|
+
host: canonical,
|
|
36
|
+
kind: hostKind(canonical),
|
|
37
|
+
port
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** This function gives the text of a target: `host`, `host:port`, or `[address]:port` for an IPv6 address. */
|
|
41
|
+
function formatTarget(t) {
|
|
42
|
+
const host = t.kind === "ipv6" && t.port !== void 0 ? `[${t.host}]` : t.host;
|
|
43
|
+
return t.port === void 0 ? host : `${host}:${t.port}`;
|
|
44
|
+
}
|
|
45
|
+
/** This function tells if two targets have the same host and the same port. */
|
|
46
|
+
function sameTarget(a, b) {
|
|
47
|
+
return a.host === b.host && a.port === b.port;
|
|
48
|
+
}
|
|
49
|
+
const LABEL = /^(?!-)[a-z0-9-]{1,63}(?<!-)$/;
|
|
50
|
+
function validName(host) {
|
|
51
|
+
if (host.length === 0 || host.length > 253) return false;
|
|
52
|
+
return host.split(".").every((label) => LABEL.test(label) || label === "_" || /^_[a-z0-9-]{1,62}$/.test(label));
|
|
53
|
+
}
|
|
54
|
+
function portOf(text) {
|
|
55
|
+
if (!/^\d{1,5}$/.test(text)) return void 0;
|
|
56
|
+
const port = Number(text);
|
|
57
|
+
return port >= 1 && port <= 65535 ? port : void 0;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* This function reads a target from the text that a user gives.
|
|
61
|
+
*
|
|
62
|
+
* It accepts a URL (`https://example.com/a`), a host (`example.com`) and a host with a port (`example.com:8443`).
|
|
63
|
+
* It also accepts an IPv4 address, and an IPv6 address in brackets with a port (`[2001:db8::1]:443`).
|
|
64
|
+
* A URL with a known scheme and no port gets the port of its scheme.
|
|
65
|
+
*/
|
|
66
|
+
function parseTarget(text) {
|
|
67
|
+
const input = text.trim();
|
|
68
|
+
if (input.length === 0) return err("The target is empty.");
|
|
69
|
+
if (input.includes("://")) {
|
|
70
|
+
let url;
|
|
71
|
+
try {
|
|
72
|
+
url = new URL(input);
|
|
73
|
+
} catch {
|
|
74
|
+
return err(`"${input}" is not a valid URL.`);
|
|
75
|
+
}
|
|
76
|
+
const host = canonicalHost(url.hostname);
|
|
77
|
+
if (host.length === 0) return err(`The URL "${input}" has no host.`);
|
|
78
|
+
return checked(host, url.port === "" ? SCHEME_PORTS.get(url.protocol) : Number(url.port), input);
|
|
79
|
+
}
|
|
80
|
+
if (isIP(input) === 6) return checked(canonicalHost(input), void 0, input);
|
|
81
|
+
const bracket = /^\[([^\]]+)\](?::(.*))?$/.exec(input);
|
|
82
|
+
if (bracket !== null) {
|
|
83
|
+
const host = bracket[1] ?? "";
|
|
84
|
+
if (isIP(host) !== 6) return err(`"${host}" is not an IPv6 address.`);
|
|
85
|
+
if (bracket[2] === void 0) return checked(canonicalHost(host), void 0, input);
|
|
86
|
+
const port = portOf(bracket[2]);
|
|
87
|
+
return port === void 0 ? err(`"${bracket[2]}" is not a port from 1 to 65535.`) : checked(canonicalHost(host), port, input);
|
|
88
|
+
}
|
|
89
|
+
const colon = input.lastIndexOf(":");
|
|
90
|
+
if (colon === -1) return checked(canonicalHost(input), void 0, input);
|
|
91
|
+
const port = portOf(input.slice(colon + 1));
|
|
92
|
+
if (port === void 0) return err(`"${input.slice(colon + 1)}" is not a port from 1 to 65535.`);
|
|
93
|
+
return checked(canonicalHost(input.slice(0, colon)), port, input);
|
|
94
|
+
}
|
|
95
|
+
function checked(host, port, input) {
|
|
96
|
+
const kind = hostKind(host);
|
|
97
|
+
if (kind === "name" && !validName(host)) return err(`"${input}" has no valid host name.`);
|
|
98
|
+
return ok(port === void 0 ? {
|
|
99
|
+
host,
|
|
100
|
+
kind
|
|
101
|
+
} : {
|
|
102
|
+
host,
|
|
103
|
+
kind,
|
|
104
|
+
port
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// src/fact.ts
|
|
108
|
+
const KIND_NAME = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;
|
|
109
|
+
/** This function makes a fact kind. The name has lower-case words with dots, for example `tls.handshake`. */
|
|
110
|
+
function defineFactKind(spec) {
|
|
111
|
+
if (!KIND_NAME.test(spec.kind)) throw new RangeError(`"${spec.kind}" is not a valid name of a fact kind`);
|
|
112
|
+
return Object.freeze({ ...spec });
|
|
113
|
+
}
|
|
114
|
+
/** This function tells if a fact is of a kind, and if its value passes the check of the kind. */
|
|
115
|
+
function isFactOf(kind, fact) {
|
|
116
|
+
return fact.kind === kind.kind && fact.layer === kind.layer && kind.check(fact.value);
|
|
117
|
+
}
|
|
118
|
+
/** This function gives the facts of one kind, in their order. */
|
|
119
|
+
function factsOf(kind, facts) {
|
|
120
|
+
return facts.filter((fact) => isFactOf(kind, fact));
|
|
121
|
+
}
|
|
122
|
+
/** This function makes an observation with a type check of its value against its kind. */
|
|
123
|
+
function observe(kind, subject, value, time) {
|
|
124
|
+
return time === void 0 ? {
|
|
125
|
+
kind,
|
|
126
|
+
subject,
|
|
127
|
+
value
|
|
128
|
+
} : {
|
|
129
|
+
kind,
|
|
130
|
+
subject,
|
|
131
|
+
value,
|
|
132
|
+
time
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// src/probe.ts
|
|
136
|
+
/** This function makes a probe, and examines its fields. */
|
|
137
|
+
function defineProbe(probe) {
|
|
138
|
+
if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(probe.id)) throw new RangeError(`"${probe.id}" is not a valid probe id`);
|
|
139
|
+
if (probe.kinds.length === 0) throw new RangeError(`The probe "${probe.id}" gives no kind of fact`);
|
|
140
|
+
return probe;
|
|
141
|
+
}
|
|
142
|
+
// src/scope.ts
|
|
143
|
+
/** The default limit of the different ports of one host in one runner. */
|
|
144
|
+
const DEFAULT_MAX_PORTS_PER_HOST = 8;
|
|
145
|
+
function parseRange(cidr) {
|
|
146
|
+
const slash = cidr.indexOf("/");
|
|
147
|
+
const address = canonicalHost(slash === -1 ? cidr : cidr.slice(0, slash));
|
|
148
|
+
const family = isIP(address);
|
|
149
|
+
if (family === 0) return err(`"${cidr}" is not an IP address or a CIDR range.`);
|
|
150
|
+
const max = family === 4 ? 32 : 128;
|
|
151
|
+
const prefixText = slash === -1 ? String(max) : cidr.slice(slash + 1);
|
|
152
|
+
if (!/^\d{1,3}$/.test(prefixText) || Number(prefixText) > max) return err(`"${cidr}" has no prefix from 0 to ${max}.`);
|
|
153
|
+
return ok({
|
|
154
|
+
address,
|
|
155
|
+
prefix: Number(prefixText),
|
|
156
|
+
family: family === 4 ? "ipv4" : "ipv6"
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** This function gives the problems of a grant, or an empty list for a correct grant. */
|
|
160
|
+
function grantProblems(grant) {
|
|
161
|
+
if (grant.kind === "range") {
|
|
162
|
+
const range = parseRange(grant.cidr);
|
|
163
|
+
return range.tag === "err" ? [range.error] : [];
|
|
164
|
+
}
|
|
165
|
+
const host = canonicalHost(grant.host);
|
|
166
|
+
if (host.length === 0) return ["A host grant has an empty host."];
|
|
167
|
+
if (isIP(host) !== 0) return [`The host grant "${grant.host}" is an IP address. Use a range grant for an address.`];
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* This function makes a scope from grants. It refuses a grant with a problem, so a typing error cannot widen the scope.
|
|
172
|
+
*
|
|
173
|
+
* The default limit of ports is `DEFAULT_MAX_PORTS_PER_HOST`.
|
|
174
|
+
*/
|
|
175
|
+
function scopeOf(grants, options = {}) {
|
|
176
|
+
const problems = grants.flatMap(grantProblems);
|
|
177
|
+
const maxPortsPerHost = options.maxPortsPerHost ?? 8;
|
|
178
|
+
if (!Number.isInteger(maxPortsPerHost) || maxPortsPerHost < 1) problems.push("The limit of ports for each host is not a positive integer.");
|
|
179
|
+
if (problems.length > 0) return err(problems);
|
|
180
|
+
return ok({
|
|
181
|
+
grants: [...grants],
|
|
182
|
+
maxPortsPerHost
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* This function gives the grant for a target that the user names. A name gets a host grant. An IP address gets a
|
|
187
|
+
* range grant of one address.
|
|
188
|
+
*/
|
|
189
|
+
function namedGrant(t, note) {
|
|
190
|
+
if (t.kind === "name") return {
|
|
191
|
+
kind: "host",
|
|
192
|
+
host: t.host,
|
|
193
|
+
basis: "named",
|
|
194
|
+
note
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
kind: "range",
|
|
198
|
+
cidr: `${t.host}/${t.kind === "ipv4" ? 32 : 128}`,
|
|
199
|
+
basis: "named",
|
|
200
|
+
note
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/** This function gives the grant of one address that a DNS answer for a granted name gave. */
|
|
204
|
+
function resolvedGrant(name, address, factId) {
|
|
205
|
+
const host = canonicalHost(address);
|
|
206
|
+
const family = isIP(host);
|
|
207
|
+
if (family === 0) throw new RangeError(`"${address}" is not an IP address`);
|
|
208
|
+
const note = factId === void 0 ? `${canonicalHost(name)} resolved to it` : `${canonicalHost(name)} resolved to it (fact ${factId})`;
|
|
209
|
+
return {
|
|
210
|
+
kind: "range",
|
|
211
|
+
cidr: `${host}/${family === 4 ? 32 : 128}`,
|
|
212
|
+
basis: "resolved",
|
|
213
|
+
note
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** This function gives a scope with more grants. The limit of ports does not change. */
|
|
217
|
+
function widen(scope, grants) {
|
|
218
|
+
return scopeOf([...scope.grants, ...grants], { maxPortsPerHost: scope.maxPortsPerHost });
|
|
219
|
+
}
|
|
220
|
+
function grantFor(scope, peer) {
|
|
221
|
+
const host = canonicalHost(peer.host);
|
|
222
|
+
const family = isIP(host);
|
|
223
|
+
for (const grant of scope.grants) {
|
|
224
|
+
if (grant.kind === "host") {
|
|
225
|
+
if (family !== 0) continue;
|
|
226
|
+
const granted = canonicalHost(grant.host);
|
|
227
|
+
if (host === granted || grant.subdomains === true && host.endsWith(`.${granted}`)) return grant;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (family === 0) continue;
|
|
231
|
+
const range = parseRange(grant.cidr);
|
|
232
|
+
if (range.tag === "err" || range.value.family === "ipv4" !== (family === 4)) continue;
|
|
233
|
+
const list = new BlockList();
|
|
234
|
+
list.addSubnet(range.value.address, range.value.prefix, range.value.family);
|
|
235
|
+
if (list.check(host, family === 4 ? "ipv4" : "ipv6")) return grant;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* This function decides if an active probe can send packets to a peer.
|
|
240
|
+
*
|
|
241
|
+
* 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.
|
|
242
|
+
* The ledger has the ports that the runner sent packets to before.
|
|
243
|
+
*/
|
|
244
|
+
function decideScope(scope, peer, ledger = /* @__PURE__ */ new Map()) {
|
|
245
|
+
const grant = grantFor(scope, peer);
|
|
246
|
+
if (grant === void 0) return {
|
|
247
|
+
allowed: false,
|
|
248
|
+
reason: `${formatTarget(peer)} is not in the scope. Name it, or add a grant for a network that you own.`
|
|
249
|
+
};
|
|
250
|
+
if (peer.port !== void 0) {
|
|
251
|
+
const ports = ledger.get(canonicalHost(peer.host)) ?? /* @__PURE__ */ new Set();
|
|
252
|
+
if (!ports.has(peer.port) && ports.size >= scope.maxPortsPerHost) return {
|
|
253
|
+
allowed: false,
|
|
254
|
+
reason: `${formatTarget(peer)} is a new port after ${ports.size} ports of the same host. netdiag does no port scan.`
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
allowed: true,
|
|
259
|
+
grant
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// src/run.ts
|
|
263
|
+
function message(thrown) {
|
|
264
|
+
if (thrown instanceof Error) return thrown.message;
|
|
265
|
+
return String(thrown);
|
|
266
|
+
}
|
|
267
|
+
function abortReason(signal) {
|
|
268
|
+
const reason = signal.reason;
|
|
269
|
+
return reason === void 0 ? "The signal stopped the run." : message(reason);
|
|
270
|
+
}
|
|
271
|
+
/** This function makes a runner. */
|
|
272
|
+
function createRunner(options) {
|
|
273
|
+
const { clock } = options;
|
|
274
|
+
const origin = {
|
|
275
|
+
wall: options.wall ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
276
|
+
monotonic: clock.now()
|
|
277
|
+
};
|
|
278
|
+
let scope = options.scope;
|
|
279
|
+
const facts = [];
|
|
280
|
+
let ledger = /* @__PURE__ */ new Map();
|
|
281
|
+
let next = 1;
|
|
282
|
+
/** This function gives the ledger with the ports of the peers, or the refusal of the first peer out of the scope. */
|
|
283
|
+
function reserve(peers) {
|
|
284
|
+
const planned = new Map([...ledger].map(([host, ports]) => [host, new Set(ports)]));
|
|
285
|
+
for (const peer of peers) {
|
|
286
|
+
const decision = decideScope(scope, peer, planned);
|
|
287
|
+
if (!decision.allowed) return {
|
|
288
|
+
peer,
|
|
289
|
+
reason: decision.reason
|
|
290
|
+
};
|
|
291
|
+
if (peer.port === void 0) continue;
|
|
292
|
+
const host = canonicalHost(peer.host);
|
|
293
|
+
planned.set(host, (planned.get(host) ?? /* @__PURE__ */ new Set()).add(peer.port));
|
|
294
|
+
}
|
|
295
|
+
return planned;
|
|
296
|
+
}
|
|
297
|
+
function stamp(probe, observations, time) {
|
|
298
|
+
const stamped = [];
|
|
299
|
+
for (const observation of observations) {
|
|
300
|
+
const { kind } = observation;
|
|
301
|
+
if (!probe.kinds.some((known) => known.kind === kind.kind)) return `The probe gave the kind "${kind.kind}", which it does not declare.`;
|
|
302
|
+
if (!kind.check(observation.value)) return `The value of a "${kind.kind}" fact does not pass the check of its kind.`;
|
|
303
|
+
stamped.push({
|
|
304
|
+
id: `f${next + stamped.length}`,
|
|
305
|
+
kind: kind.kind,
|
|
306
|
+
layer: kind.layer,
|
|
307
|
+
subject: observation.subject,
|
|
308
|
+
time: observation.time ?? time,
|
|
309
|
+
source: probe.tool === void 0 ? {
|
|
310
|
+
probe: probe.id,
|
|
311
|
+
version: probe.version
|
|
312
|
+
} : {
|
|
313
|
+
probe: probe.id,
|
|
314
|
+
version: probe.version,
|
|
315
|
+
tool: probe.tool
|
|
316
|
+
},
|
|
317
|
+
value: observation.value
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return stamped;
|
|
321
|
+
}
|
|
322
|
+
async function run(probe, input, signal = new AbortController().signal) {
|
|
323
|
+
if (signal.aborted) return {
|
|
324
|
+
status: "aborted",
|
|
325
|
+
reason: abortReason(signal)
|
|
326
|
+
};
|
|
327
|
+
let peers;
|
|
328
|
+
try {
|
|
329
|
+
peers = probe.peers(input);
|
|
330
|
+
} catch (thrown) {
|
|
331
|
+
return {
|
|
332
|
+
status: "failed",
|
|
333
|
+
stage: "probe",
|
|
334
|
+
error: message(thrown)
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
if (probe.mode === "passive" && peers.length > 0) return {
|
|
338
|
+
status: "failed",
|
|
339
|
+
stage: "probe",
|
|
340
|
+
error: `The passive probe "${probe.id}" gives peers: ${peers.map(formatTarget).join(", ")}.`
|
|
341
|
+
};
|
|
342
|
+
if (probe.mode === "active" && peers.length === 0) return {
|
|
343
|
+
status: "failed",
|
|
344
|
+
stage: "probe",
|
|
345
|
+
error: `The active probe "${probe.id}" gives no peer.`
|
|
346
|
+
};
|
|
347
|
+
const reserved = reserve(peers);
|
|
348
|
+
if ("reason" in reserved) return {
|
|
349
|
+
status: "refused",
|
|
350
|
+
peer: reserved.peer,
|
|
351
|
+
reason: reserved.reason
|
|
352
|
+
};
|
|
353
|
+
ledger = reserved;
|
|
354
|
+
const context = {
|
|
355
|
+
clock,
|
|
356
|
+
signal
|
|
357
|
+
};
|
|
358
|
+
let availability;
|
|
359
|
+
try {
|
|
360
|
+
availability = await probe.available(context);
|
|
361
|
+
} catch (thrown) {
|
|
362
|
+
return {
|
|
363
|
+
status: "failed",
|
|
364
|
+
stage: "probe",
|
|
365
|
+
error: message(thrown)
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (!availability.available) return {
|
|
369
|
+
status: "unavailable",
|
|
370
|
+
reason: availability.reason
|
|
371
|
+
};
|
|
372
|
+
if (signal.aborted) return {
|
|
373
|
+
status: "aborted",
|
|
374
|
+
reason: abortReason(signal)
|
|
375
|
+
};
|
|
376
|
+
const start = clock.now();
|
|
377
|
+
let raw;
|
|
378
|
+
try {
|
|
379
|
+
raw = await probe.run(input, context);
|
|
380
|
+
} catch (thrown) {
|
|
381
|
+
if (signal.aborted) return {
|
|
382
|
+
status: "aborted",
|
|
383
|
+
reason: abortReason(signal)
|
|
384
|
+
};
|
|
385
|
+
return {
|
|
386
|
+
status: "failed",
|
|
387
|
+
stage: "run",
|
|
388
|
+
error: message(thrown)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
const time = {
|
|
392
|
+
start,
|
|
393
|
+
end: clock.now()
|
|
394
|
+
};
|
|
395
|
+
let observations;
|
|
396
|
+
try {
|
|
397
|
+
observations = probe.parse(raw, input);
|
|
398
|
+
} catch (thrown) {
|
|
399
|
+
return {
|
|
400
|
+
status: "failed",
|
|
401
|
+
stage: "parse",
|
|
402
|
+
error: message(thrown),
|
|
403
|
+
raw
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const stamped = stamp(probe, observations, time);
|
|
407
|
+
if (typeof stamped === "string") return {
|
|
408
|
+
status: "failed",
|
|
409
|
+
stage: "parse",
|
|
410
|
+
error: stamped,
|
|
411
|
+
raw
|
|
412
|
+
};
|
|
413
|
+
facts.push(...stamped);
|
|
414
|
+
next += stamped.length;
|
|
415
|
+
return {
|
|
416
|
+
status: "done",
|
|
417
|
+
time,
|
|
418
|
+
raw,
|
|
419
|
+
facts: stamped
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
return {
|
|
423
|
+
origin,
|
|
424
|
+
scope: () => scope,
|
|
425
|
+
facts: () => [...facts],
|
|
426
|
+
grant(grants) {
|
|
427
|
+
const widened = widen(scope, grants);
|
|
428
|
+
if (widened.tag === "err") throw new RangeError(widened.error.join(" "));
|
|
429
|
+
scope = widened.value;
|
|
430
|
+
},
|
|
431
|
+
run
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
export { DEFAULT_MAX_PORTS_PER_HOST, FLOOR, LAYERS, NONE, PARTS, STANDARD_CATALOG, STANDARD_CATALOG_SPEC, STANDARD_FAULTS, STANDARD_FEATURES, STRENGTHS, UNKNOWN, canonicalHost, compileCatalog, createRunner, decideScope, defineFactKind, defineProbe, diagnose, expand, explain, factsOf, formatTarget, grantProblems, hostKind, isFactOf, isLayer, layerRank, namedGrant, observe, parseTarget, predictive, resolvedGrant, sameTarget, scopeOf, target, widen };
|
|
435
|
+
|
|
436
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/target.ts","../src/fact.ts","../src/probe.ts","../src/scope.ts","../src/run.ts"],"sourcesContent":["import { isIP } from \"node:net\";\nimport { err, ok, type Result } from \"@mark1russell7/optional\";\n\n/** The kind of the host of a target: a DNS name, an IPv4 address or an IPv6 address. */\nexport type HostKind = \"name\" | \"ipv4\" | \"ipv6\";\n\n/**\n * A host that a probe examines, and an optional port.\n *\n * The host is in its canonical form: lower case, with no final dot and no brackets around an IPv6 address.\n */\nexport interface Target {\n readonly host: string;\n readonly kind: HostKind;\n readonly port?: number | undefined;\n}\n\n/** The ports of the URL schemes that a target can have. */\nconst SCHEME_PORTS: ReadonlyMap<string, number> = new Map([\n [\"http:\", 80],\n [\"https:\", 443],\n [\"ws:\", 80],\n [\"wss:\", 443],\n [\"dns:\", 53],\n [\"tls:\", 443],\n]);\n\n/** This function gives the kind of a host in its canonical form. */\nexport function hostKind(host: string): HostKind {\n const family = isIP(host);\n if (family === 4) return \"ipv4\";\n if (family === 6) return \"ipv6\";\n return \"name\";\n}\n\n/** This function gives the canonical form of a host: lower case, with no final dot and no brackets. */\nexport function canonicalHost(host: string): string {\n let text = host.trim().toLowerCase();\n if (text.startsWith(\"[\") && text.endsWith(\"]\")) text = text.slice(1, -1);\n if (text.endsWith(\".\")) text = text.slice(0, -1);\n return text;\n}\n\n/** This function makes a target from a host and an optional port. */\nexport function target(host: string, port?: number): Target {\n const canonical = canonicalHost(host);\n return port === undefined ? { host: canonical, kind: hostKind(canonical) } : { host: canonical, kind: hostKind(canonical), port };\n}\n\n/** This function gives the text of a target: `host`, `host:port`, or `[address]:port` for an IPv6 address. */\nexport function formatTarget(t: Target): string {\n const host = t.kind === \"ipv6\" && t.port !== undefined ? `[${t.host}]` : t.host;\n return t.port === undefined ? host : `${host}:${t.port}`;\n}\n\n/** This function tells if two targets have the same host and the same port. */\nexport function sameTarget(a: Target, b: Target): boolean {\n return a.host === b.host && a.port === b.port;\n}\n\nconst LABEL = /^(?!-)[a-z0-9-]{1,63}(?<!-)$/;\n\nfunction validName(host: string): boolean {\n if (host.length === 0 || host.length > 253) return false;\n return host.split(\".\").every((label) => LABEL.test(label) || label === \"_\" || /^_[a-z0-9-]{1,62}$/.test(label));\n}\n\nfunction portOf(text: string): number | undefined {\n if (!/^\\d{1,5}$/.test(text)) return undefined;\n const port = Number(text);\n return port >= 1 && port <= 65535 ? port : undefined;\n}\n\n/**\n * This function reads a target from the text that a user gives.\n *\n * It accepts a URL (`https://example.com/a`), a host (`example.com`) and a host with a port (`example.com:8443`).\n * It also accepts an IPv4 address, and an IPv6 address in brackets with a port (`[2001:db8::1]:443`).\n * A URL with a known scheme and no port gets the port of its scheme.\n */\nexport function parseTarget(text: string): Result<Target, string> {\n const input = text.trim();\n if (input.length === 0) return err(\"The target is empty.\");\n if (input.includes(\"://\")) {\n let url: URL;\n try {\n url = new URL(input);\n } catch {\n return err(`\"${input}\" is not a valid URL.`);\n }\n const host = canonicalHost(url.hostname);\n if (host.length === 0) return err(`The URL \"${input}\" has no host.`);\n const port = url.port === \"\" ? SCHEME_PORTS.get(url.protocol) : Number(url.port);\n return checked(host, port, input);\n }\n if (isIP(input) === 6) return checked(canonicalHost(input), undefined, input);\n const bracket = /^\\[([^\\]]+)\\](?::(.*))?$/.exec(input);\n if (bracket !== null) {\n const host = bracket[1] ?? \"\";\n if (isIP(host) !== 6) return err(`\"${host}\" is not an IPv6 address.`);\n if (bracket[2] === undefined) return checked(canonicalHost(host), undefined, input);\n const port = portOf(bracket[2]);\n return port === undefined ? err(`\"${bracket[2]}\" is not a port from 1 to 65535.`) : checked(canonicalHost(host), port, input);\n }\n const colon = input.lastIndexOf(\":\");\n if (colon === -1) return checked(canonicalHost(input), undefined, input);\n const port = portOf(input.slice(colon + 1));\n if (port === undefined) return err(`\"${input.slice(colon + 1)}\" is not a port from 1 to 65535.`);\n return checked(canonicalHost(input.slice(0, colon)), port, input);\n}\n\nfunction checked(host: string, port: number | undefined, input: string): Result<Target, string> {\n const kind = hostKind(host);\n if (kind === \"name\" && !validName(host)) return err(`\"${input}\" has no valid host name.`);\n return ok(port === undefined ? { host, kind } : { host, kind, port });\n}\n","import type { Layer } from \"./layer.ts\";\nimport type { Target } from \"./target.ts\";\n\n/**\n * The time of a fact, in milliseconds on the monotonic clock of the runner.\n *\n * The field `origin` of the runner gives the wall-clock time of one monotonic time. Thus each fact time has a wall-clock time.\n */\nexport interface FactTime {\n readonly start: number;\n readonly end: number;\n}\n\n/** The source of a fact: the probe and its version, and the outside tool when the probe starts one. */\nexport interface FactSource {\n readonly probe: string;\n readonly version: string;\n readonly tool?: string | undefined;\n}\n\n/**\n * A typed fact of one layer of the network stack.\n *\n * A fact has a layer, a time, a source and a value. Its kind names the type of its value, for example `tcp.connect`.\n * The subject is the target of the fact. A fact is plain data, so a record of facts is JSON.\n */\nexport interface Fact<V = unknown> {\n /** The id is unique in the facts of one runner, for example `f12`. */\n readonly id: string;\n readonly kind: string;\n readonly layer: Layer;\n readonly subject: Target;\n readonly time: FactTime;\n readonly source: FactSource;\n readonly value: V;\n}\n\n/**\n * The definition of a kind of fact: its name, its layer and the check of its value.\n *\n * The check is the contract of the value. The runner refuses a value that does not pass it, thus each fact of a kind has a\n * value of the type `V`.\n */\nexport interface FactKind<V> {\n readonly kind: string;\n readonly layer: Layer;\n readonly summary: string;\n readonly check: (value: unknown) => value is V;\n}\n\n/** The value type of a fact kind. */\nexport type ValueOf<K> = K extends FactKind<infer V> ? V : never;\n\nconst KIND_NAME = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/;\n\n/** This function makes a fact kind. The name has lower-case words with dots, for example `tls.handshake`. */\nexport function defineFactKind<V>(spec: FactKind<V>): FactKind<V> {\n if (!KIND_NAME.test(spec.kind)) throw new RangeError(`\"${spec.kind}\" is not a valid name of a fact kind`);\n return Object.freeze({ ...spec });\n}\n\n/** This function tells if a fact is of a kind, and if its value passes the check of the kind. */\nexport function isFactOf<V>(kind: FactKind<V>, fact: Fact): fact is Fact<V> {\n return fact.kind === kind.kind && fact.layer === kind.layer && kind.check(fact.value);\n}\n\n/** This function gives the facts of one kind, in their order. */\nexport function factsOf<V>(kind: FactKind<V>, facts: readonly Fact[]): Fact<V>[] {\n return facts.filter((fact): fact is Fact<V> => isFactOf(kind, fact));\n}\n\n/**\n * An observation is a fact that a probe parsed, before the runner gives it an id, a layer and a source.\n *\n * The time is optional. Without a time, the fact gets the time of the run of the probe.\n */\nexport interface Observation<V = unknown> {\n readonly kind: FactKind<V>;\n readonly subject: Target;\n readonly value: V;\n readonly time?: FactTime | undefined;\n}\n\n/** This function makes an observation with a type check of its value against its kind. */\nexport function observe<V>(kind: FactKind<V>, subject: Target, value: V, time?: FactTime): Observation<V> {\n return time === undefined ? { kind, subject, value } : { kind, subject, value, time };\n}\n","import type { Clock } from \"@mark1russell7/algebra/clock\";\nimport type { FactKind, Observation } from \"./fact.ts\";\nimport type { Layer } from \"./layer.ts\";\nimport type { Target } from \"./target.ts\";\n\n/**\n * The mode of a probe.\n *\n * - `active`: the probe sends packets to its peers, for example a TCP connect.\n * - `passive`: the probe reads only the state of this computer, for example its routes. It has no peers.\n */\nexport type ProbeMode = \"active\" | \"passive\";\n\n/** The context of a run: the clock of the runner, and the signal that stops the run. */\nexport interface ProbeContext {\n readonly clock: Clock;\n readonly signal: AbortSignal;\n}\n\n/** The availability of a probe on this computer. An unavailable probe tells why, for example a tool that is not installed. */\nexport type Availability =\n | { readonly available: true; readonly version?: string | undefined }\n | { readonly available: false; readonly reason: string };\n\n/**\n * The contract of a probe. A probe is an adapter: it tells if it is available, it does its work, and it parses its raw output into\n * observations.\n *\n * - `peers` gives each host that `run` sends packets to. A passive probe gives no peers. The runner examines each peer\n * against the scope before `run` starts.\n * - `run` does the effects and gives the raw output. A network failure is an observation, not an error. For example, a\n * TCP connect that times out gives the outcome `timeout`. `run` throws only for a fault of the probe.\n * - `parse` is a pure function of the raw output and the input. Thus a record of a raw output gives the same facts again,\n * also after a correction of the parser.\n *\n * The input and the raw output are plain JSON data, so a record can keep them.\n */\nexport interface Probe<I, R> {\n /** The id has lower-case words with dots, for example `tcp.connect`. */\n readonly id: string;\n /** The version of the probe changes when its facts change. */\n readonly version: string;\n readonly summary: string;\n readonly layer: Layer;\n readonly mode: ProbeMode;\n /** The kinds of the facts that the probe gives. The runner refuses an observation of a different kind. */\n readonly kinds: readonly FactKind<unknown>[];\n /** The outside tool that the probe starts, for example `ping`. A native probe has no tool. */\n readonly tool?: string | undefined;\n peers(input: I): readonly Target[];\n available(context: ProbeContext): Promise<Availability>;\n run(input: I, context: ProbeContext): Promise<R>;\n parse(raw: R, input: I): readonly Observation[];\n}\n\n/** This function makes a probe, and examines its fields. */\nexport function defineProbe<I, R>(probe: Probe<I, R>): Probe<I, R> {\n if (!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(probe.id)) throw new RangeError(`\"${probe.id}\" is not a valid probe id`);\n if (probe.kinds.length === 0) throw new RangeError(`The probe \"${probe.id}\" gives no kind of fact`);\n return probe;\n}\n","import { BlockList, isIP } from \"node:net\";\nimport { err, ok, type Result } from \"@mark1russell7/optional\";\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\ninterface Range {\n readonly address: string;\n readonly prefix: number;\n readonly family: \"ipv4\" | \"ipv6\";\n}\n\nfunction parseRange(cidr: string): Result<Range, string> {\n const slash = cidr.indexOf(\"/\");\n const address = canonicalHost(slash === -1 ? cidr : cidr.slice(0, slash));\n const family = isIP(address);\n if (family === 0) return err(`\"${cidr}\" is not an IP address or a CIDR range.`);\n const max = family === 4 ? 32 : 128;\n const prefixText = slash === -1 ? String(max) : cidr.slice(slash + 1);\n if (!/^\\d{1,3}$/.test(prefixText) || Number(prefixText) > max) return err(`\"${cidr}\" has no prefix from 0 to ${max}.`);\n return ok({ address, prefix: Number(prefixText), family: family === 4 ? \"ipv4\" : \"ipv6\" });\n}\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 = parseRange(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 = parseRange(grant.cidr);\n if (range.tag === \"err\" || (range.value.family === \"ipv4\") !== (family === 4)) continue;\n const list = new BlockList();\n list.addSubnet(range.value.address, range.value.prefix, range.value.family);\n if (list.check(host, family === 4 ? \"ipv4\" : \"ipv6\")) 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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,72 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package",
|
|
2
3
|
"name": "@mark1russell7/netdiag",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Network diagnosis: typed facts at each layer of the stack, probes as adapters, and a scope rule for active probes.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"network",
|
|
8
|
+
"diagnosis",
|
|
9
|
+
"dns",
|
|
10
|
+
"tcp",
|
|
11
|
+
"tls",
|
|
12
|
+
"http",
|
|
13
|
+
"probe",
|
|
14
|
+
"typescript"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/mark1russell7/netdiag#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/mark1russell7/netdiag/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/mark1russell7/netdiag.git",
|
|
23
|
+
"directory": "packages/netdiag"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"author": "Mark Russell (https://github.com/mark1russell7)",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"main": "./build/index.js",
|
|
30
|
+
"types": "./build/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./build/index.d.ts",
|
|
34
|
+
"default": "./build/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./diagnosis": {
|
|
37
|
+
"types": "./build/diagnosis.d.ts",
|
|
38
|
+
"default": "./build/diagnosis.js"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"build",
|
|
44
|
+
"src",
|
|
45
|
+
"!src/**/*.test.ts"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22.12"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@mark1russell7/algebra": "^0.8.8",
|
|
55
|
+
"@mark1russell7/optional": "^1.2.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@mark1russell7/cue": "^2.0.0",
|
|
59
|
+
"@types/node": "^26.6.5",
|
|
60
|
+
"@vitest/coverage-v8": "^5.0.3",
|
|
61
|
+
"typescript": "^7.0.2",
|
|
62
|
+
"vitest": "^5.0.3",
|
|
63
|
+
"tsdown": "^0.23.7",
|
|
64
|
+
"fast-check": "^4.10.2"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"build": "tsdown",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"test:watch": "vitest",
|
|
70
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.test.json"
|
|
71
|
+
}
|
|
6
72
|
}
|