@takodotid/circuit 0.34.3 → 0.36.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/README.md +14 -15
- package/dist/chunks/index-20kcjxjg.js +174 -0
- package/dist/chunks/index-2r4bbhac.js +977 -0
- package/dist/chunks/index-n7w3tg5y.js +149 -0
- package/dist/chunks/new-w3zys5y4.js +172 -0
- package/dist/chunks/server-gzsf5me5.js +259 -0
- package/dist/cli/index.js +11345 -9024
- package/dist/helpers/index.js +594 -0
- package/dist/index.js +9 -28
- package/dist/types/core/{addresses.d.ts → address-space.d.ts} +23 -7
- package/dist/types/core/address.d.ts +44 -0
- package/dist/types/core/cabling.d.ts +41 -0
- package/dist/types/core/cross-checks.d.ts +28 -0
- package/dist/types/core/define.d.ts +25 -3
- package/dist/types/core/project-files.d.ts +12 -0
- package/dist/types/core/schema-docs.d.ts +13 -4
- package/dist/types/core/secrets.d.ts +62 -21
- package/dist/types/core/{graph.d.ts → topology.d.ts} +32 -12
- package/dist/types/core/utils.d.ts +8 -0
- package/dist/types/core/validator.d.ts +19 -0
- package/dist/types/helpers/bgp.d.ts +97 -0
- package/dist/types/helpers/checks.d.ts +32 -0
- package/dist/types/helpers/exchange.d.ts +49 -0
- package/dist/types/helpers/filters.d.ts +51 -0
- package/dist/types/helpers/index.d.ts +10 -0
- package/dist/types/helpers/route-server.d.ts +52 -0
- package/dist/types/index.d.ts +5 -3
- package/dist/types/integrations/librenms.d.ts +60 -0
- package/dist/types/integrations/netbox.d.ts +89 -0
- package/dist/types/integrations/peeringdb.d.ts +24 -0
- package/dist/types/integrations/prometheus.d.ts +24 -0
- package/dist/types/integrations/registries.d.ts +8 -0
- package/dist/types/platforms/bgp-wait.d.ts +18 -0
- package/dist/types/platforms/catalog.d.ts +109 -0
- package/dist/types/platforms/derive.d.ts +17 -0
- package/dist/types/platforms/fortios/firewall.d.ts +45 -0
- package/dist/types/platforms/fortios/index.d.ts +134 -0
- package/dist/types/platforms/fortios/names.d.ts +52 -0
- package/dist/types/platforms/fortios/plan.d.ts +104 -0
- package/dist/types/platforms/fortios/render.d.ts +75 -0
- package/dist/types/platforms/fortios/routing.d.ts +56 -0
- package/dist/types/platforms/fortios/tree.d.ts +37 -0
- package/dist/types/platforms/index.d.ts +13 -0
- package/dist/types/platforms/junos/bgp.d.ts +44 -0
- package/dist/types/platforms/junos/index.d.ts +114 -0
- package/dist/types/platforms/junos/render.d.ts +154 -0
- package/dist/types/platforms/line-diff.d.ts +67 -0
- package/dist/types/platforms/platform.d.ts +120 -0
- package/dist/types/platforms/raisecom-ros/index.d.ts +71 -0
- package/dist/types/platforms/raisecom-ros/render.d.ts +41 -0
- package/dist/types/platforms/routeros/export.d.ts +59 -0
- package/dist/types/platforms/routeros/index.d.ts +96 -0
- package/dist/types/{adapters/devices → platforms}/routeros/models.d.ts +4 -0
- package/dist/types/platforms/routeros/plan.d.ts +54 -0
- package/dist/types/platforms/routeros/render.d.ts +240 -0
- package/dist/types/platforms/vrp/index.d.ts +83 -0
- package/dist/types/platforms/vrp/render.d.ts +96 -0
- package/dist/types/schema/common.d.ts +12 -6
- package/dist/types/schema/index.d.ts +1 -0
- package/dist/types/schema/interfaces.d.ts +14 -6
- package/dist/types/schema/policy.d.ts +18 -10
- package/dist/types/schema/routing.d.ts +14 -1
- package/dist/types/schema/system.d.ts +16 -6
- package/dist/types/schema/time-zones.d.ts +2 -0
- package/dist/types/transport/console.d.ts +79 -23
- package/dist/types/transport/path-check.d.ts +32 -0
- package/dist/types/transport/ssh.d.ts +17 -15
- package/package.json +18 -12
- package/src/cli/graph-format.ts +91 -0
- package/src/cli/index.ts +1074 -484
- package/src/cli/new.ts +208 -175
- package/src/cli/rename.ts +97 -91
- package/src/core/address-space.ts +320 -0
- package/src/core/address.ts +202 -0
- package/src/core/cabling.ts +154 -0
- package/src/core/cross-checks.ts +364 -0
- package/src/core/define.ts +26 -3
- package/src/core/project-files.ts +35 -0
- package/src/core/schema-docs.ts +138 -130
- package/src/core/secrets.ts +170 -76
- package/src/core/topology.ts +656 -0
- package/src/core/utils.ts +37 -0
- package/src/core/validator.ts +407 -0
- package/src/helpers/bgp.ts +301 -0
- package/src/helpers/checks.ts +178 -0
- package/src/helpers/exchange.ts +117 -0
- package/src/helpers/filters.ts +107 -0
- package/src/helpers/index.ts +12 -0
- package/src/helpers/route-server.ts +212 -0
- package/src/index.ts +6 -3
- package/src/integrations/librenms.ts +291 -0
- package/src/integrations/netbox.ts +620 -0
- package/src/integrations/peeringdb.ts +173 -0
- package/src/integrations/prometheus.ts +90 -0
- package/src/integrations/registries.ts +25 -0
- package/src/platforms/bgp-wait.ts +41 -0
- package/src/platforms/catalog.ts +69 -0
- package/src/platforms/derive.ts +117 -0
- package/src/{adapters/devices → platforms}/fortios/TRAPS.md +6 -0
- package/src/platforms/fortios/firewall.ts +377 -0
- package/src/platforms/fortios/index.ts +1060 -0
- package/src/platforms/fortios/names.ts +130 -0
- package/src/platforms/fortios/plan.ts +929 -0
- package/src/platforms/fortios/render.ts +779 -0
- package/src/platforms/fortios/routing.ts +476 -0
- package/src/platforms/fortios/tree.ts +129 -0
- package/src/platforms/index.ts +36 -0
- package/src/{adapters/devices → platforms}/junos/TRAPS.md +8 -0
- package/src/platforms/junos/bgp.ts +293 -0
- package/src/platforms/junos/index.ts +1006 -0
- package/src/platforms/junos/render.ts +967 -0
- package/src/platforms/line-diff.ts +244 -0
- package/src/platforms/platform.ts +122 -0
- package/src/platforms/raisecom-ros/TRAPS.md +40 -0
- package/src/platforms/raisecom-ros/index.ts +731 -0
- package/src/platforms/raisecom-ros/render.ts +328 -0
- package/src/{adapters/devices → platforms}/routeros/TRAPS.md +16 -2
- package/src/platforms/routeros/export.ts +342 -0
- package/src/platforms/routeros/index.ts +636 -0
- package/src/{adapters/devices → platforms}/routeros/models.ts +2 -0
- package/src/platforms/routeros/plan.ts +444 -0
- package/src/platforms/routeros/render.ts +1483 -0
- package/src/platforms/vrp/TRAPS.md +45 -0
- package/src/platforms/vrp/index.ts +652 -0
- package/src/platforms/vrp/render.ts +615 -0
- package/src/schema/common.ts +15 -6
- package/src/schema/index.ts +1 -0
- package/src/schema/interfaces.ts +15 -5
- package/src/schema/policy.ts +22 -10
- package/src/schema/routing.ts +16 -1
- package/src/schema/system.ts +16 -6
- package/src/schema/time-zones.ts +482 -0
- package/src/studio/data.ts +172 -168
- package/src/studio/git.ts +95 -90
- package/src/studio/public/app.js +161 -150
- package/src/studio/public/compare.js +219 -212
- package/src/studio/public/diff.js +77 -75
- package/src/studio/public/dom.js +118 -108
- package/src/studio/public/evaluate.js +584 -545
- package/src/studio/public/export.js +120 -118
- package/src/studio/public/graph.js +1343 -1266
- package/src/studio/public/net.js +115 -109
- package/src/studio/public/tables.js +1135 -1083
- package/src/studio/public/tools.js +561 -529
- package/src/studio/server.ts +202 -196
- package/src/transport/console.ts +363 -271
- package/src/transport/path-check.ts +91 -0
- package/src/transport/ssh.ts +98 -94
- package/templates/colocation/checks.ts +11 -6
- package/templates/colocation/edge-01.ts +9 -9
- package/templates/colocation/routing.ts +2 -2
- package/templates/colocation/tenancy.ts +3 -3
- package/templates/colocation/tor-01.ts +3 -3
- package/templates/edge-router/checks.ts +4 -4
- package/templates/edge-router/edge-01.ts +5 -5
- package/templates/edge-router/routing.ts +2 -2
- package/templates/project/AGENTS.md +5 -1
- package/templates/single-site/checks.ts +2 -2
- package/dist/chunks/index-47debt16.js +0 -956
- package/dist/chunks/index-v8zgr7wm.js +0 -128
- package/dist/chunks/new-ny89s7y7.js +0 -166
- package/dist/chunks/server-tjbb3rqc.js +0 -251
- package/dist/presets/index.js +0 -394
- package/dist/types/adapters/devices/catalog.d.ts +0 -86
- package/dist/types/adapters/devices/derive.d.ts +0 -7
- package/dist/types/adapters/devices/fortios/firewall.d.ts +0 -23
- package/dist/types/adapters/devices/fortios/index.d.ts +0 -58
- package/dist/types/adapters/devices/fortios/names.d.ts +0 -48
- package/dist/types/adapters/devices/fortios/plan.d.ts +0 -28
- package/dist/types/adapters/devices/fortios/render.d.ts +0 -38
- package/dist/types/adapters/devices/fortios/routing.d.ts +0 -32
- package/dist/types/adapters/devices/fortios/tree.d.ts +0 -33
- package/dist/types/adapters/devices/index.d.ts +0 -3
- package/dist/types/adapters/devices/junos/bgp.d.ts +0 -11
- package/dist/types/adapters/devices/junos/index.d.ts +0 -42
- package/dist/types/adapters/devices/junos/render.d.ts +0 -52
- package/dist/types/adapters/devices/lines.d.ts +0 -56
- package/dist/types/adapters/devices/raisecom-ros/index.d.ts +0 -28
- package/dist/types/adapters/devices/routeros/export.d.ts +0 -39
- package/dist/types/adapters/devices/routeros/index.d.ts +0 -30
- package/dist/types/adapters/devices/routeros/plan.d.ts +0 -4
- package/dist/types/adapters/devices/routeros/render.d.ts +0 -100
- package/dist/types/adapters/devices/types.d.ts +0 -40
- package/dist/types/adapters/devices/vrp/index.d.ts +0 -27
- package/dist/types/adapters/registries/index.d.ts +0 -3
- package/dist/types/adapters/registries/librenms.d.ts +0 -15
- package/dist/types/adapters/registries/peeringdb.d.ts +0 -16
- package/dist/types/adapters/registries/prometheus.d.ts +0 -16
- package/dist/types/core/across.d.ts +0 -4
- package/dist/types/core/addr.d.ts +0 -34
- package/dist/types/core/files.d.ts +0 -6
- package/dist/types/core/merge.d.ts +0 -4
- package/dist/types/core/time.d.ts +0 -2
- package/dist/types/core/validate.d.ts +0 -2
- package/dist/types/presets/index.d.ts +0 -183
- package/src/adapters/devices/catalog.ts +0 -41
- package/src/adapters/devices/derive.ts +0 -59
- package/src/adapters/devices/fortios/firewall.ts +0 -362
- package/src/adapters/devices/fortios/index.ts +0 -923
- package/src/adapters/devices/fortios/names.ts +0 -115
- package/src/adapters/devices/fortios/plan.ts +0 -888
- package/src/adapters/devices/fortios/render.ts +0 -768
- package/src/adapters/devices/fortios/routing.ts +0 -444
- package/src/adapters/devices/fortios/tree.ts +0 -121
- package/src/adapters/devices/index.ts +0 -9
- package/src/adapters/devices/junos/bgp.ts +0 -269
- package/src/adapters/devices/junos/index.ts +0 -862
- package/src/adapters/devices/junos/render.ts +0 -872
- package/src/adapters/devices/lines.ts +0 -212
- package/src/adapters/devices/raisecom-ros/TRAPS.md +0 -26
- package/src/adapters/devices/raisecom-ros/index.ts +0 -700
- package/src/adapters/devices/routeros/export.ts +0 -321
- package/src/adapters/devices/routeros/index.ts +0 -346
- package/src/adapters/devices/routeros/plan.ts +0 -398
- package/src/adapters/devices/routeros/render.ts +0 -1490
- package/src/adapters/devices/types.ts +0 -38
- package/src/adapters/devices/vrp/TRAPS.md +0 -26
- package/src/adapters/devices/vrp/index.ts +0 -865
- package/src/adapters/registries/index.ts +0 -19
- package/src/adapters/registries/librenms.ts +0 -92
- package/src/adapters/registries/peeringdb.ts +0 -169
- package/src/adapters/registries/prometheus.ts +0 -71
- package/src/cli/graph.ts +0 -89
- package/src/core/across.ts +0 -350
- package/src/core/addr.ts +0 -174
- package/src/core/addresses.ts +0 -316
- package/src/core/files.ts +0 -32
- package/src/core/graph.ts +0 -640
- package/src/core/merge.ts +0 -19
- package/src/core/time.ts +0 -15
- package/src/core/validate.ts +0 -348
- package/src/presets/index.ts +0 -579
- /package/dist/types/{adapters/devices → platforms}/fortios/models.d.ts +0 -0
- /package/dist/types/{adapters/devices → platforms}/junos/models.d.ts +0 -0
- /package/dist/types/{adapters/devices → platforms}/raisecom-ros/models.d.ts +0 -0
- /package/dist/types/{adapters/devices → platforms}/vrp/models.d.ts +0 -0
- /package/src/{adapters/devices → platforms}/fortios/models.ts +0 -0
- /package/src/{adapters/devices → platforms}/junos/models.ts +0 -0
- /package/src/{adapters/devices → platforms}/raisecom-ros/models.ts +0 -0
- /package/src/{adapters/devices → platforms}/vrp/models.ts +0 -0
package/src/core/addresses.ts
DELETED
|
@@ -1,316 +0,0 @@
|
|
|
1
|
-
// Every address the config uses, by prefix, and what is still free: what `circuit addresses` prints. It reads the config only, so an address a device gave out by itself, such as a DHCP lease, is not known.
|
|
2
|
-
|
|
3
|
-
import type { Device, Family } from "../schema";
|
|
4
|
-
import {
|
|
5
|
-
contains,
|
|
6
|
-
familyOf,
|
|
7
|
-
formatAddress,
|
|
8
|
-
host,
|
|
9
|
-
isAddress,
|
|
10
|
-
isCidr,
|
|
11
|
-
network as prefixOf,
|
|
12
|
-
parse,
|
|
13
|
-
prefixesBetween,
|
|
14
|
-
rangeOf,
|
|
15
|
-
split,
|
|
16
|
-
} from "./addr";
|
|
17
|
-
import { holdersOf } from "./graph";
|
|
18
|
-
|
|
19
|
-
/** One address or range in a segment, and what holds it. */
|
|
20
|
-
export type Use = {
|
|
21
|
-
/** An address, or `first-last` for a range. */
|
|
22
|
-
address: string;
|
|
23
|
-
what: string;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/** A prefix our devices have addresses in. */
|
|
27
|
-
export type Segment = {
|
|
28
|
-
prefix: string;
|
|
29
|
-
uses: Use[];
|
|
30
|
-
/** How many addresses no one uses, as a number written out, since an IPv6 count does not fit a JavaScript number. */
|
|
31
|
-
free: string;
|
|
32
|
-
/** The lowest free address, when there is one. */
|
|
33
|
-
next?: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/** A prefix in use without being a segment: a loopback's address, or a block routed elsewhere. */
|
|
37
|
-
export type Block = {
|
|
38
|
-
prefix: string;
|
|
39
|
-
what: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** Space the network owns: a prefix it originates in BGP, or one asked for. Everything in use inside it, and what is free. */
|
|
43
|
-
export type Space = {
|
|
44
|
-
prefix: string;
|
|
45
|
-
/** Why it is listed, such as `originated by edge-01`. */
|
|
46
|
-
owner: string;
|
|
47
|
-
segments: Segment[];
|
|
48
|
-
blocks: Block[];
|
|
49
|
-
/** The fewest prefixes that cover what nothing uses. */
|
|
50
|
-
free: string[];
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export type AddressReport = {
|
|
54
|
-
spaces: Space[];
|
|
55
|
-
/** What is in use outside every space. Left out when a prefix is asked for. */
|
|
56
|
-
elsewhere: { segments: Segment[]; blocks: Block[] };
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/** A use as a range of numbers, for counting. */
|
|
60
|
-
type Taken = { first: bigint; last: bigint };
|
|
61
|
-
|
|
62
|
-
const bits = (family: Family) => (family === "ipv4" ? 32 : 128);
|
|
63
|
-
const isHostRoute = (cidr: string) => split(cidr).len === bits(split(cidr).family);
|
|
64
|
-
const numberOf = (address: string) => parse(address)!;
|
|
65
|
-
|
|
66
|
-
/** Addresses a segment can give out: IPv4 leaves out the network and broadcast addresses below /31, and IPv6 the subnet-router anycast address. */
|
|
67
|
-
function usable(prefix: string): Taken {
|
|
68
|
-
const { first, last, family } = rangeOf(prefix);
|
|
69
|
-
const { len } = split(prefix);
|
|
70
|
-
|
|
71
|
-
if (family === "ipv4" && len <= 30) return { first: first + 1n, last: last - 1n };
|
|
72
|
-
if (family === "ipv6" && len < 127) return { first: first + 1n, last };
|
|
73
|
-
|
|
74
|
-
return { first, last };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Ranges merged, so overlapping ones count once. */
|
|
78
|
-
function merged(ranges: Taken[]): Taken[] {
|
|
79
|
-
const sorted = [...ranges].sort((a, b) => (a.first < b.first ? -1 : a.first > b.first ? 1 : 0));
|
|
80
|
-
const result: Taken[] = [];
|
|
81
|
-
|
|
82
|
-
for (const range of sorted) {
|
|
83
|
-
const previous = result.at(-1);
|
|
84
|
-
if (previous && range.first <= previous.last + 1n) previous.last = range.last > previous.last ? range.last : previous.last;
|
|
85
|
-
else result.push({ ...range });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** What is inside `within` and not in any of the ranges. */
|
|
92
|
-
function gaps(within: Taken, taken: Taken[]): Taken[] {
|
|
93
|
-
const result: Taken[] = [];
|
|
94
|
-
let cursor = within.first;
|
|
95
|
-
|
|
96
|
-
for (const range of merged(taken)) {
|
|
97
|
-
if (range.last < within.first || range.first > within.last) continue;
|
|
98
|
-
if (range.first > cursor) result.push({ first: cursor, last: range.first - 1n });
|
|
99
|
-
if (range.last + 1n > cursor) cursor = range.last + 1n;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (cursor <= within.last) result.push({ first: cursor, last: within.last });
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** `12` or, for a count too long to read, `about 2^64`, to the nearest power of two. */
|
|
107
|
-
function count(value: bigint): string {
|
|
108
|
-
if (value <= 1n << 20n) return String(value);
|
|
109
|
-
|
|
110
|
-
const power = value.toString(2).length - 1;
|
|
111
|
-
const nearer = value - (1n << BigInt(power)) >= 1n << BigInt(power - 1) ? power + 1 : power;
|
|
112
|
-
return `about 2^${nearer}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const byAddress = (a: Use, b: Use) => {
|
|
116
|
-
const first = (use: Use) => numberOf(use.address.split("-")[0]!);
|
|
117
|
-
return first(a) < first(b) ? -1 : first(a) > first(b) ? 1 : 0;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/** IPv4 before IPv6, then by first address. */
|
|
121
|
-
function comparePrefixes(a: string, b: string): number {
|
|
122
|
-
const [ra, rb] = [rangeOf(a), rangeOf(b)];
|
|
123
|
-
if (ra.family !== rb.family) return ra.family === "ipv4" ? -1 : 1;
|
|
124
|
-
return ra.first < rb.first ? -1 : ra.first > rb.first ? 1 : 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const byPrefix = (a: { prefix: string }, b: { prefix: string }) => comparePrefixes(a.prefix, b.prefix);
|
|
128
|
-
|
|
129
|
-
/** Every address in use, by segment, under the space the network owns. With `within`, only what falls inside that prefix, as the one space. */
|
|
130
|
-
export function addressesOf(devices: readonly Device[], within?: string): AddressReport {
|
|
131
|
-
const segments = new Map<string, { uses: Use[]; taken: Taken[] }>();
|
|
132
|
-
const blocks: Block[] = [];
|
|
133
|
-
|
|
134
|
-
const segmentOf = (address: string) => [...segments.keys()].find((prefix) => contains(prefix, address));
|
|
135
|
-
|
|
136
|
-
// Segments come from the addresses on interfaces; a host route, such as a loopback's, is a block of its own.
|
|
137
|
-
for (const holder of holdersOf(devices)) {
|
|
138
|
-
const vrf = holder.device.ports?.[holder.name]?.vrf ?? holder.device.interfaces?.[holder.name]?.vrf;
|
|
139
|
-
const what = `${holder.device.name} ${holder.name}${vrf ? `, VRF ${vrf}` : ""}`;
|
|
140
|
-
|
|
141
|
-
if (isHostRoute(holder.address) || holder.peer) {
|
|
142
|
-
blocks.push({ prefix: `${host(holder.address)}/${bits(familyOf(holder.address))}`, what });
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const prefix = prefixOf(holder.address);
|
|
147
|
-
const segment = segments.get(prefix) ?? { uses: [], taken: [] };
|
|
148
|
-
const value = numberOf(host(holder.address));
|
|
149
|
-
|
|
150
|
-
segment.uses.push({ address: host(holder.address), what });
|
|
151
|
-
segment.taken.push({ first: value, last: value });
|
|
152
|
-
segments.set(prefix, segment);
|
|
153
|
-
|
|
154
|
-
for (const group of holder.vrrp) {
|
|
155
|
-
if (!isAddress(group.address) || !contains(prefix, group.address)) continue;
|
|
156
|
-
if (segment.uses.some((use) => use.address === group.address)) continue;
|
|
157
|
-
|
|
158
|
-
const holders = devices.filter((device) =>
|
|
159
|
-
Object.values({ ...device.ports, ...device.interfaces }).some((settings) =>
|
|
160
|
-
settings?.vrrp?.some((other) => other.id === group.id && other.address === group.address)
|
|
161
|
-
)
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
segment.uses.push({ address: group.address, what: `VRRP ${group.id} on ${holders.map((device) => device.name).join(", ")}` });
|
|
165
|
-
segment.taken.push({ first: numberOf(group.address), last: numberOf(group.address) });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// A use inside a segment is an address taken; outside every segment, a block of its own. An address one of our interfaces holds is listed once, as that interface.
|
|
170
|
-
const take = (address: string, what: string, first = address, last = address) => {
|
|
171
|
-
const prefix = segmentOf(first);
|
|
172
|
-
if (!prefix) return false;
|
|
173
|
-
|
|
174
|
-
const segment = segments.get(prefix)!;
|
|
175
|
-
if (segment.uses.some((use) => use.address === address)) return true;
|
|
176
|
-
|
|
177
|
-
segment.uses.push({ address, what });
|
|
178
|
-
segment.taken.push({ first: numberOf(first), last: numberOf(last) });
|
|
179
|
-
return true;
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
for (const device of devices) {
|
|
183
|
-
for (const [name, server] of Object.entries(device.dhcp ?? {})) {
|
|
184
|
-
const [start, end] = server.pool;
|
|
185
|
-
if (isAddress(start) && isAddress(end)) take(`${start}-${end}`, `DHCP pool ${name} on ${device.name}`, start, end);
|
|
186
|
-
|
|
187
|
-
for (const reservation of server.reservations ?? []) {
|
|
188
|
-
if (isAddress(reservation.address))
|
|
189
|
-
take(reservation.address, `reserved for ${reservation.description ?? reservation.mac} by ${device.name}`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
for (const route of device.routing?.static ?? []) {
|
|
194
|
-
if (route.via && isAddress(route.via)) take(route.via, `next hop for ${route.prefix} on ${device.name}`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
for (const [name, neighbor] of Object.entries(device.routing?.bgp?.neighbors ?? {})) {
|
|
198
|
-
if (isAddress(neighbor.address)) take(neighbor.address, `BGP neighbor ${name} of ${device.name}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
|
|
202
|
-
if (iface.type !== "wireguard") continue;
|
|
203
|
-
|
|
204
|
-
for (const peer of iface.peers ?? []) {
|
|
205
|
-
for (const allowed of peer.allowed_addresses) {
|
|
206
|
-
if (!isCidr(allowed)) continue;
|
|
207
|
-
|
|
208
|
-
const what = `WireGuard peer ${peer.name} on ${device.name} ${name}`;
|
|
209
|
-
if (isHostRoute(allowed) && take(host(allowed), what)) continue;
|
|
210
|
-
if (!segmentOf(host(allowed))) blocks.push({ prefix: prefixOf(allowed), what });
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Space the network owns: what it originates in BGP, or the prefix asked for.
|
|
217
|
-
const owners = new Map<string, string[]>();
|
|
218
|
-
for (const device of devices) {
|
|
219
|
-
for (const prefix of device.routing?.bgp?.networks ?? []) {
|
|
220
|
-
if (!isCidr(prefix)) continue;
|
|
221
|
-
owners.set(prefixOf(prefix), [...(owners.get(prefixOf(prefix)) ?? []), device.name]);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// A static route to a prefix no segment is in hands that block to whoever is at the next hop. A route covering segments or owned space, such as an aggregate, is not a use.
|
|
226
|
-
for (const device of devices) {
|
|
227
|
-
for (const route of device.routing?.static ?? []) {
|
|
228
|
-
if (!isCidr(route.prefix) || split(route.prefix).len === 0) continue;
|
|
229
|
-
|
|
230
|
-
const overlapsSegment = [...segments.keys()].some((prefix) => contains(route.prefix, prefix) || contains(prefix, route.prefix));
|
|
231
|
-
const coversOwned = [...owners.keys()].some((owned) => contains(route.prefix, owned));
|
|
232
|
-
if (overlapsSegment || coversOwned) continue;
|
|
233
|
-
|
|
234
|
-
const how = route.blackhole ? "discarded" : route.via ? `routed via ${route.via}` : `routed out ${route.interface}`;
|
|
235
|
-
blocks.push({ prefix: prefixOf(route.prefix), what: `${how} by ${device.name}` });
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const asked = within ? prefixOf(within) : undefined;
|
|
240
|
-
const spacePrefixes = asked ? [asked] : [...owners.keys()];
|
|
241
|
-
|
|
242
|
-
// A segment or block belongs to the smallest space it is inside.
|
|
243
|
-
const spaceOf = (prefix: string) =>
|
|
244
|
-
spacePrefixes.filter((space) => contains(space, prefix)).sort((a, b) => split(b).len - split(a).len)[0];
|
|
245
|
-
|
|
246
|
-
const toSegment = (prefix: string): Segment => {
|
|
247
|
-
const { uses, taken } = segments.get(prefix)!;
|
|
248
|
-
const free = gaps(usable(prefix), taken);
|
|
249
|
-
const total = free.reduce((sum, range) => sum + range.last - range.first + 1n, 0n);
|
|
250
|
-
const family = familyOf(prefix);
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
prefix,
|
|
254
|
-
uses: uses.sort(byAddress),
|
|
255
|
-
free: count(total),
|
|
256
|
-
...(free[0] ? { next: formatAddress(free[0].first, family) } : {}),
|
|
257
|
-
};
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const uniqueBlocks = blocks.filter(
|
|
261
|
-
(block, index) => blocks.findIndex((other) => other.prefix === block.prefix && other.what === block.what) === index
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
const spaces: Space[] = spacePrefixes.sort(comparePrefixes).map((space) => {
|
|
265
|
-
const inside = (prefix: string) => spaceOf(prefix) === space;
|
|
266
|
-
const spaceSegments = [...segments.keys()].filter(inside).sort(comparePrefixes);
|
|
267
|
-
const spaceBlocks = uniqueBlocks.filter((block) => inside(block.prefix)).sort(byPrefix);
|
|
268
|
-
|
|
269
|
-
// Smaller spaces inside this one are in use too.
|
|
270
|
-
const nested = spacePrefixes.filter((other) => other !== space && contains(space, other));
|
|
271
|
-
const taken = [...spaceSegments, ...spaceBlocks.map((block) => block.prefix), ...nested].map((prefix) => rangeOf(prefix));
|
|
272
|
-
const family = familyOf(space);
|
|
273
|
-
|
|
274
|
-
return {
|
|
275
|
-
prefix: space,
|
|
276
|
-
owner: asked && !owners.has(space) ? "asked for" : `originated by ${(owners.get(space) ?? []).join(", ")}`,
|
|
277
|
-
segments: spaceSegments.map(toSegment),
|
|
278
|
-
blocks: spaceBlocks,
|
|
279
|
-
free: gaps(rangeOf(space), taken).flatMap((range) => prefixesBetween(range.first, range.last, family)),
|
|
280
|
-
};
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
const outside = (prefix: string) => !spaceOf(prefix);
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
spaces,
|
|
287
|
-
elsewhere: asked
|
|
288
|
-
? { segments: [], blocks: [] }
|
|
289
|
-
: {
|
|
290
|
-
segments: [...segments.keys()].filter(outside).sort(comparePrefixes).map(toSegment),
|
|
291
|
-
blocks: uniqueBlocks.filter((block) => outside(block.prefix)).sort(byPrefix),
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** Free prefixes of one length inside the free space, up to `limit`, and how many there are in all. */
|
|
297
|
-
export function freeOfLength(free: readonly string[], length: number, limit: number): { prefixes: string[]; total: bigint } {
|
|
298
|
-
const prefixes: string[] = [];
|
|
299
|
-
let total = 0n;
|
|
300
|
-
|
|
301
|
-
for (const prefix of free) {
|
|
302
|
-
const { len, family } = split(prefix);
|
|
303
|
-
if (len > length || length > bits(family)) continue;
|
|
304
|
-
|
|
305
|
-
const { first } = rangeOf(prefix);
|
|
306
|
-
const step = 1n << BigInt(bits(family) - length);
|
|
307
|
-
const fits = 1n << BigInt(length - len);
|
|
308
|
-
total += fits;
|
|
309
|
-
|
|
310
|
-
for (let index = 0n; index < fits && prefixes.length < limit; index++) {
|
|
311
|
-
prefixes.push(`${formatAddress(first + index * step, family)}/${length}`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return { prefixes, total };
|
|
316
|
-
}
|
package/src/core/files.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// A project's own TypeScript files, and which of them declares a device: for renaming a device everywhere, and for a device's history.
|
|
2
|
-
|
|
3
|
-
import { readdirSync, readFileSync } from "node:fs";
|
|
4
|
-
import { join, relative } from "node:path";
|
|
5
|
-
|
|
6
|
-
/** Directories that hold no config of the project's own. */
|
|
7
|
-
const SKIPPED = new Set(["node_modules", ".git", ".circuit", "dist"]);
|
|
8
|
-
|
|
9
|
-
/** Every TypeScript file of the project, relative to its root, in name order. */
|
|
10
|
-
export function projectSources(root: string, directory = root): string[] {
|
|
11
|
-
const entries = readdirSync(directory, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
12
|
-
|
|
13
|
-
return entries.flatMap((entry) => {
|
|
14
|
-
if (entry.isDirectory()) return SKIPPED.has(entry.name) ? [] : projectSources(root, join(directory, entry.name));
|
|
15
|
-
return /\.(ts|mts|cts)$/.test(entry.name) ? [relative(root, join(directory, entry.name))] : [];
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** The first name written out after a call to defineDevice: the device's own, not that of another device a link, a peer or a tunnel names further on. */
|
|
20
|
-
const FIRST_NAME = /^[\s\S]*?\bname\s*:\s*(["'`])(.*?)\1/;
|
|
21
|
-
|
|
22
|
-
/** Whether a file's text declares the device: it calls defineDevice and gives it the name, written out whole. */
|
|
23
|
-
export function declaresDevice(text: string, name: string): boolean {
|
|
24
|
-
return [...text.matchAll(/\bdefineDevice\s*\(/g)].some(
|
|
25
|
-
(call) => FIRST_NAME.exec(text.slice(call.index + call[0].length))?.[2] === name
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** The project files that declare the device, relative to the root. */
|
|
30
|
-
export function deviceFiles(root: string, name: string, sources = projectSources(root)): string[] {
|
|
31
|
-
return sources.filter((file) => declaresDevice(readFileSync(join(root, file), "utf8"), name));
|
|
32
|
-
}
|