@takodotid/circuit 0.43.1 → 1.0.0-rc.2
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 +115 -37
- package/package.json +21 -58
- package/src/checks/across.ts +402 -0
- package/src/checks/built-in.ts +256 -0
- package/src/checks/device.ts +436 -0
- package/src/checks/index.ts +53 -0
- package/src/cli/advisories.ts +108 -0
- package/src/cli/app.ts +71 -0
- package/src/cli/editor-types.generated.ts +30 -0
- package/src/cli/editor-types.ts +69 -0
- package/src/cli/format.ts +124 -0
- package/src/cli/git-hooks.ts +79 -0
- package/src/cli/index.ts +350 -1170
- package/src/cli/migrate.ts +554 -0
- package/src/cli/project-commands.ts +656 -0
- package/src/cli/releases.ts +374 -0
- package/src/cli/remember.ts +339 -0
- package/src/cli/rename.ts +27 -33
- package/src/cli/secrets-password.ts +60 -0
- package/src/cli/secrets.ts +258 -0
- package/src/cli/setup.ts +300 -0
- package/src/cli/signing.ts +23 -0
- package/src/cli/text-editor.ts +268 -0
- package/src/cli/ui.ts +298 -0
- package/src/cli/version.ts +2 -0
- package/src/cli/versions.ts +158 -0
- package/src/config/address.ts +102 -0
- package/src/config/circuit.d.ts +30 -0
- package/src/config/common.ts +65 -0
- package/src/config/communities.ts +479 -0
- package/src/config/define.ts +684 -0
- package/src/config/device.ts +164 -0
- package/src/config/firewall.ts +150 -0
- package/src/config/index.ts +22 -0
- package/src/{schema → config}/interfaces.ts +121 -85
- package/src/config/keys.ts +434 -0
- package/src/config/management.ts +61 -0
- package/src/config/merge.ts +335 -0
- package/src/config/models.ts +70 -0
- package/src/config/monitoring.ts +64 -0
- package/src/config/network.ts +180 -0
- package/src/config/presets.ts +149 -0
- package/src/config/routing.ts +401 -0
- package/src/config/services.ts +51 -0
- package/src/config/switching.ts +84 -0
- package/src/config/system.ts +103 -0
- package/src/{schema → config}/time-zones.ts +1 -1
- package/src/core/address.ts +1 -1
- package/src/core/cabling.ts +8 -43
- package/src/core/graph.ts +40 -0
- package/src/{integrations → core}/registries.ts +5 -3
- package/src/core/secrets-file.ts +539 -0
- package/src/core/secrets.ts +83 -64
- package/src/core/topology.ts +103 -146
- package/src/core/utils.ts +12 -1
- package/src/model.ts +83 -0
- package/src/platforms/derive.ts +64 -37
- package/src/platforms/finding.ts +9 -0
- package/src/platforms/fortios/firewall.ts +76 -64
- package/src/platforms/fortios/index.ts +189 -125
- package/src/platforms/fortios/names.ts +51 -18
- package/src/platforms/fortios/render.ts +69 -62
- package/src/platforms/fortios/routing.ts +89 -58
- package/src/platforms/fortios/vdoms.ts +47 -16
- package/src/platforms/junos/TRAPS.md +2 -2
- package/src/platforms/junos/bgp.ts +63 -42
- package/src/platforms/junos/index.ts +122 -89
- package/src/platforms/junos/render.ts +139 -104
- package/src/platforms/platform.ts +10 -8
- package/src/platforms/raisecom-ros/TRAPS.md +1 -1
- package/src/platforms/raisecom-ros/index.ts +89 -70
- package/src/platforms/raisecom-ros/render.ts +49 -45
- package/src/platforms/routeros/TRAPS.md +3 -3
- package/src/platforms/routeros/index.ts +121 -60
- package/src/platforms/routeros/render.ts +214 -195
- package/src/platforms/vrp/TRAPS.md +1 -1
- package/src/platforms/vrp/index.ts +74 -50
- package/src/platforms/vrp/render.ts +67 -56
- package/src/project/project.ts +725 -0
- package/src/studio/app/env.d.ts +9 -0
- package/src/studio/app/index.html +18 -0
- package/src/studio/app/src/App.vue +95 -0
- package/src/studio/app/src/components/AddressesView.vue +38 -0
- package/src/studio/app/src/components/CablesView.vue +29 -0
- package/src/studio/app/src/components/CompareView.vue +160 -0
- package/src/studio/app/src/components/DataTable.vue +49 -0
- package/src/studio/app/src/components/DeviceView.vue +237 -0
- package/src/studio/app/src/components/DevicesView.vue +32 -0
- package/src/studio/app/src/components/Faceplate.vue +75 -0
- package/src/studio/app/src/components/GraphView.vue +282 -0
- package/src/studio/app/src/components/LineDiff.vue +24 -0
- package/src/studio/app/src/components/ProblemsView.vue +42 -0
- package/src/studio/app/src/components/SearchView.vue +72 -0
- package/src/studio/app/src/components/SecretsView.vue +112 -0
- package/src/studio/app/src/components/SessionsView.vue +35 -0
- package/src/studio/app/src/components/Settings.vue +95 -0
- package/src/studio/app/src/components/ToolsView.vue +118 -0
- package/src/studio/app/src/components/VlansView.vue +50 -0
- package/src/studio/app/src/lib/csv.ts +19 -0
- package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
- package/src/studio/app/src/lib/format.ts +38 -0
- package/src/studio/app/src/lib/net.ts +68 -0
- package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
- package/src/studio/app/src/lib/store.ts +71 -0
- package/src/studio/app/src/lib/tools.ts +217 -0
- package/src/studio/app/src/main.ts +9 -0
- package/src/studio/{public → app/src}/style.css +14 -4
- package/src/studio/app/tsconfig.json +17 -0
- package/src/studio/app/vite.config.ts +16 -0
- package/src/studio/assets.generated.ts +3 -0
- package/src/studio/data.ts +96 -188
- package/src/studio/git.ts +27 -43
- package/src/studio/server.ts +196 -107
- package/src/studio/types.ts +141 -0
- package/src/transport/console.ts +3 -3
- package/src/transport/ssh.ts +1 -1
- package/dist/chunks/index-3b343xva.js +0 -177
- package/dist/chunks/index-6wb41q67.js +0 -62
- package/dist/chunks/index-n7w3tg5y.js +0 -149
- package/dist/chunks/index-yzw86rc8.js +0 -921
- package/dist/chunks/new-w3zys5y4.js +0 -172
- package/dist/chunks/server-cxr9ynxc.js +0 -259
- package/dist/cli/index.js +0 -12922
- package/dist/helpers/index.js +0 -881
- package/dist/index.js +0 -28
- package/dist/types/core/address-space.d.ts +0 -62
- package/dist/types/core/address.d.ts +0 -44
- package/dist/types/core/cabling.d.ts +0 -41
- package/dist/types/core/cross-checks.d.ts +0 -28
- package/dist/types/core/define.d.ts +0 -79
- package/dist/types/core/project-files.d.ts +0 -12
- package/dist/types/core/schema-docs.d.ts +0 -25
- package/dist/types/core/secrets.d.ts +0 -62
- package/dist/types/core/topology.d.ts +0 -96
- package/dist/types/core/utils.d.ts +0 -10
- package/dist/types/core/validator.d.ts +0 -23
- package/dist/types/helpers/bgp.d.ts +0 -195
- package/dist/types/helpers/cables.d.ts +0 -69
- package/dist/types/helpers/checks.d.ts +0 -36
- package/dist/types/helpers/exchange.d.ts +0 -55
- package/dist/types/helpers/filters.d.ts +0 -51
- package/dist/types/helpers/index.d.ts +0 -14
- package/dist/types/helpers/route-server.d.ts +0 -52
- package/dist/types/helpers/upstreams.d.ts +0 -192
- package/dist/types/index.d.ts +0 -10
- package/dist/types/integrations/librenms.d.ts +0 -60
- package/dist/types/integrations/netbox.d.ts +0 -89
- package/dist/types/integrations/peeringdb.d.ts +0 -24
- package/dist/types/integrations/prometheus.d.ts +0 -24
- package/dist/types/integrations/registries.d.ts +0 -8
- package/dist/types/platforms/bgp-wait.d.ts +0 -24
- package/dist/types/platforms/catalog.d.ts +0 -109
- package/dist/types/platforms/derive.d.ts +0 -17
- package/dist/types/platforms/fortios/firewall.d.ts +0 -47
- package/dist/types/platforms/fortios/index.d.ts +0 -140
- package/dist/types/platforms/fortios/models.d.ts +0 -14
- package/dist/types/platforms/fortios/names.d.ts +0 -52
- package/dist/types/platforms/fortios/plan.d.ts +0 -114
- package/dist/types/platforms/fortios/render.d.ts +0 -77
- package/dist/types/platforms/fortios/routing.d.ts +0 -56
- package/dist/types/platforms/fortios/tree.d.ts +0 -37
- package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
- package/dist/types/platforms/index.d.ts +0 -13
- package/dist/types/platforms/junos/bgp.d.ts +0 -44
- package/dist/types/platforms/junos/index.d.ts +0 -114
- package/dist/types/platforms/junos/models.d.ts +0 -14
- package/dist/types/platforms/junos/render.d.ts +0 -154
- package/dist/types/platforms/line-diff.d.ts +0 -67
- package/dist/types/platforms/platform.d.ts +0 -135
- package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
- package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
- package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
- package/dist/types/platforms/routeros/export.d.ts +0 -59
- package/dist/types/platforms/routeros/index.d.ts +0 -116
- package/dist/types/platforms/routeros/models.d.ts +0 -18
- package/dist/types/platforms/routeros/plan.d.ts +0 -58
- package/dist/types/platforms/routeros/render.d.ts +0 -250
- package/dist/types/platforms/vrp/index.d.ts +0 -83
- package/dist/types/platforms/vrp/models.d.ts +0 -12
- package/dist/types/platforms/vrp/render.d.ts +0 -96
- package/dist/types/schema/common.d.ts +0 -30
- package/dist/types/schema/device.d.ts +0 -126
- package/dist/types/schema/firewall.d.ts +0 -131
- package/dist/types/schema/index.d.ts +0 -8
- package/dist/types/schema/interfaces.d.ts +0 -205
- package/dist/types/schema/policy.d.ts +0 -107
- package/dist/types/schema/routing.d.ts +0 -161
- package/dist/types/schema/system.d.ts +0 -198
- package/dist/types/schema/time-zones.d.ts +0 -2
- package/dist/types/transport/console.d.ts +0 -115
- package/dist/types/transport/path-check.d.ts +0 -32
- package/dist/types/transport/ssh.d.ts +0 -27
- package/src/cli/graph-format.ts +0 -91
- package/src/cli/new.ts +0 -234
- package/src/core/address-space.ts +0 -320
- package/src/core/cross-checks.ts +0 -369
- package/src/core/define.ts +0 -124
- package/src/core/project-files.ts +0 -35
- package/src/core/schema-docs.ts +0 -174
- package/src/core/validator.ts +0 -508
- package/src/helpers/bgp.ts +0 -473
- package/src/helpers/cables.ts +0 -74
- package/src/helpers/checks.ts +0 -201
- package/src/helpers/exchange.ts +0 -123
- package/src/helpers/filters.ts +0 -107
- package/src/helpers/index.ts +0 -25
- package/src/helpers/route-server.ts +0 -212
- package/src/helpers/upstreams.ts +0 -369
- package/src/index.ts +0 -13
- package/src/integrations/librenms.ts +0 -291
- package/src/integrations/netbox.ts +0 -620
- package/src/integrations/peeringdb.ts +0 -173
- package/src/integrations/prometheus.ts +0 -90
- package/src/schema/common.ts +0 -41
- package/src/schema/device.ts +0 -145
- package/src/schema/firewall.ts +0 -140
- package/src/schema/index.ts +0 -8
- package/src/schema/policy.ts +0 -115
- package/src/schema/routing.ts +0 -173
- package/src/schema/system.ts +0 -203
- package/src/studio/public/app.js +0 -186
- package/src/studio/public/compare.js +0 -250
- package/src/studio/public/dom.js +0 -138
- package/src/studio/public/evaluate.js +0 -687
- package/src/studio/public/export.js +0 -130
- package/src/studio/public/graph.js +0 -1541
- package/src/studio/public/index.html +0 -83
- package/src/studio/public/net.js +0 -134
- package/src/studio/public/tables.js +0 -1289
- package/src/studio/public/tools.js +0 -610
- package/templates/colocation/checks.ts +0 -53
- package/templates/colocation/circuit.config.ts +0 -9
- package/templates/colocation/edge-01.ts +0 -133
- package/templates/colocation/routing.ts +0 -13
- package/templates/colocation/tenancy.ts +0 -76
- package/templates/colocation/tenants/acme.ts +0 -19
- package/templates/colocation/tenants/index.ts +0 -6
- package/templates/colocation/tor-01.ts +0 -33
- package/templates/edge-router/checks.ts +0 -20
- package/templates/edge-router/circuit.config.ts +0 -8
- package/templates/edge-router/edge-01.ts +0 -110
- package/templates/edge-router/routing.ts +0 -13
- package/templates/project/AGENTS.md +0 -51
- package/templates/project/CLAUDE.md +0 -1
- package/templates/project/README.md +0 -16
- package/templates/project/gitignore +0 -5
- package/templates/project/tsconfig.json +0 -13
- package/templates/single-site/checks.ts +0 -26
- package/templates/single-site/circuit.config.ts +0 -8
- package/templates/single-site/router.ts +0 -86
- package/templates/single-site/site.ts +0 -18
- package/templates/single-site/switch.ts +0 -34
- /package/src/studio/{public → app/public}/favicon.svg +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
- /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
|
@@ -6,7 +6,7 @@ import type { Client, ClientChannel } from "ssh2";
|
|
|
6
6
|
import { Address } from "../../core/address";
|
|
7
7
|
import type { Neighbor } from "../../core/cabling";
|
|
8
8
|
import { Secrets } from "../../core/secrets";
|
|
9
|
-
import type { Device, Family,
|
|
9
|
+
import type { Device, Family, Lag, Port, Routed } from "../../model";
|
|
10
10
|
import { ConsoleShell, SerialConsole, type ConsoleDialect } from "../../transport/console";
|
|
11
11
|
import { Ssh } from "../../transport/ssh";
|
|
12
12
|
import { Catalog } from "../catalog";
|
|
@@ -127,26 +127,29 @@ export class Junos extends Platform {
|
|
|
127
127
|
|
|
128
128
|
unsupported(device: Device): string[] {
|
|
129
129
|
const found: string[] = [];
|
|
130
|
+
const interfaces = Derive.interfaces(device);
|
|
130
131
|
|
|
131
|
-
for (const
|
|
132
|
-
if (
|
|
133
|
-
if (
|
|
132
|
+
for (const { name, kind } of interfaces) {
|
|
133
|
+
if (kind === "bridge") found.push(`interfaces.bridge.${name}, a bridge, which only FortiOS builds for now`);
|
|
134
|
+
if (kind === "vlan_interface" && device.interfaces?.vlan_interface?.[name]?.mac)
|
|
135
|
+
found.push(`interfaces.vlan_interface.${name}.mac, which only RouterOS sets for now`);
|
|
134
136
|
}
|
|
135
137
|
const names = new JunosNames(device);
|
|
136
|
-
const users = device.users ?? {};
|
|
138
|
+
const users = device.system?.users ?? {};
|
|
137
139
|
|
|
138
|
-
const root =
|
|
139
|
-
if (!root) found.push("users without root, the platform needs a root password");
|
|
140
|
-
if (root && root.role !== "admin") found.push("users.root.role, root is always admin");
|
|
141
|
-
if (root?.description) found.push("users.root.description");
|
|
140
|
+
const root = users.root;
|
|
141
|
+
if (!root) found.push("system.users without root, the platform needs a root password");
|
|
142
|
+
if (root && root.role !== "admin") found.push("system.users.root.role, root is always admin");
|
|
143
|
+
if (root?.description) found.push("system.users.root.description");
|
|
142
144
|
if (device.connection.user === "root") found.push("connection.user root, which logs in to a shell rather than the CLI");
|
|
143
145
|
|
|
144
146
|
for (const [name, user] of Object.entries(users)) {
|
|
145
|
-
if (
|
|
147
|
+
if (!user) continue;
|
|
148
|
+
if (Junos.SYSTEM_ACCOUNTS.has(name)) found.push(`system.users.${name}, a name the platform keeps for a system account`);
|
|
146
149
|
|
|
147
150
|
for (const key of user.ssh_keys ?? []) {
|
|
148
151
|
const kind = key.split(" ")[0]!;
|
|
149
|
-
if (!JunosRender.KEY_STATEMENT[kind]) found.push(`users.${name}.ssh_keys, a key of type ${kind}`);
|
|
152
|
+
if (!JunosRender.KEY_STATEMENT[kind]) found.push(`system.users.${name}.ssh_keys, a key of type ${kind}`);
|
|
150
153
|
}
|
|
151
154
|
}
|
|
152
155
|
|
|
@@ -158,29 +161,28 @@ export class Junos extends Platform {
|
|
|
158
161
|
|
|
159
162
|
if (management.console) found.push("management.console, the console uses the device's users");
|
|
160
163
|
if (management.interfaces) found.push("management.interfaces, which only FortiOS builds for now");
|
|
161
|
-
if (management.
|
|
164
|
+
if (management.native_protocol?.interfaces?.length) found.push("management.native_protocol.interfaces, NETCONF runs over IP only");
|
|
162
165
|
if (management.ssh?.auth_timeout || management.ssh?.auth_retries) found.push("management.ssh.auth_timeout and auth_retries");
|
|
163
166
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
if (management.telnet?.port) found.push("management.telnet.port");
|
|
168
|
+
if (management.ftp?.port) found.push("management.ftp.port");
|
|
169
|
+
if (device.monitoring?.snmp?.port) found.push("monitoring.snmp.port");
|
|
167
170
|
|
|
168
171
|
const system = device.system ?? {};
|
|
169
172
|
|
|
170
173
|
if (system.release_channel) found.push("system.release_channel");
|
|
171
|
-
if (
|
|
172
|
-
if (
|
|
173
|
-
if (system.
|
|
174
|
+
if (device.monitoring?.transceivers !== undefined) found.push("monitoring.transceivers, it is always on");
|
|
175
|
+
if (device.services?.dns_server) found.push("services.dns_server");
|
|
176
|
+
if (system.ip_stack?.syn_cookies) found.push("system.ip_stack.syn_cookies");
|
|
174
177
|
if (system.logging?.local?.lines_per_file !== undefined) found.push("system.logging.local.lines_per_file, files rotate by size");
|
|
175
178
|
|
|
176
179
|
// Time is answered only to its servers, which the filter names by address.
|
|
177
|
-
if (system.ntp && !
|
|
178
|
-
found.push("system.ntp.servers by name
|
|
180
|
+
if (system.ntp && !device.services?.ntp_server && system.ntp.servers.some((server) => !Address.isValid(server))) {
|
|
181
|
+
found.push("system.ntp.servers by name without services.ntp_server, the filter keeping others out needs addresses");
|
|
179
182
|
}
|
|
180
183
|
|
|
181
|
-
for (const [name, port] of
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
for (const [name, port] of Derive.ports(device)) {
|
|
185
|
+
const where = `interfaces.ethernet.${name}`;
|
|
184
186
|
const physical = names.port(name);
|
|
185
187
|
|
|
186
188
|
if (physical.startsWith("em")) {
|
|
@@ -188,119 +190,135 @@ export class Junos extends Platform {
|
|
|
188
190
|
"access_vlan",
|
|
189
191
|
"trunk_vlans",
|
|
190
192
|
"native_vlan",
|
|
191
|
-
"
|
|
193
|
+
"in_lag",
|
|
192
194
|
"stp",
|
|
193
195
|
"storm_control",
|
|
194
196
|
"speed",
|
|
195
197
|
"vrrp",
|
|
196
198
|
"vrf",
|
|
197
199
|
"mtu",
|
|
198
|
-
"
|
|
200
|
+
"ingress_acl",
|
|
199
201
|
"ipv6_ra",
|
|
200
202
|
] as const) {
|
|
201
|
-
if (port[field] !== undefined) found.push(
|
|
203
|
+
if (port[field] !== undefined) found.push(`${where}.${field}, a management port only routes`);
|
|
202
204
|
}
|
|
203
205
|
}
|
|
204
206
|
|
|
205
207
|
if (port.speed && !(port.speed === "1g" && physical.startsWith("ge-")))
|
|
206
|
-
found.push(
|
|
208
|
+
found.push(`${where}.speed ${port.speed}, only a 1G optic in a 10G cage`);
|
|
207
209
|
|
|
208
|
-
if (port.hardware_offload !== undefined) found.push(
|
|
210
|
+
if (port.hardware_offload !== undefined) found.push(`${where}.hardware_offload`);
|
|
209
211
|
|
|
210
212
|
// A port filter cannot match the VLAN a frame is switched in: `learn-vlan-id` is refused on an ingress port.
|
|
211
|
-
const acl = port.
|
|
212
|
-
if (acl?.some((rule) => rule.
|
|
213
|
+
const acl = port.ingress_acl ? device.switching?.acls?.[port.ingress_acl] : undefined;
|
|
214
|
+
if (acl?.some((rule) => rule.if?.vlan)) found.push(`switching.acls.${port.ingress_acl} matching a VLAN, on port ${name}`);
|
|
213
215
|
|
|
214
|
-
found.push(...Junos.routedGaps(
|
|
215
|
-
found.push(...Junos.switchedGaps(
|
|
216
|
+
found.push(...Junos.routedGaps(where, port));
|
|
217
|
+
found.push(...Junos.switchedGaps(where, port, device));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// A rule's match takes one address and one VLAN here; a list is one rule per value.
|
|
221
|
+
for (const [name, rules] of Object.entries(device.switching?.acls ?? {})) {
|
|
222
|
+
(rules ?? []).forEach((rule, index) => {
|
|
223
|
+
for (const field of ["src", "dst", "vlan"] as const) {
|
|
224
|
+
if (Array.isArray(rule.if?.[field]))
|
|
225
|
+
found.push(`switching.acls.${name}[${index}].if.${field}, a list: write one rule for each`);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
216
228
|
}
|
|
217
229
|
|
|
218
230
|
const loopbackTables = new Set<string>();
|
|
219
231
|
|
|
220
|
-
for (const
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
for (const { name, kind, settings } of interfaces) {
|
|
233
|
+
const where = `interfaces.${kind}.${name}`;
|
|
234
|
+
|
|
235
|
+
if (kind === "vxlan" || kind === "wireguard") {
|
|
236
|
+
found.push(`${where}, a ${kind}`);
|
|
223
237
|
continue;
|
|
224
238
|
}
|
|
225
239
|
|
|
226
|
-
if (
|
|
227
|
-
if (
|
|
228
|
-
if (
|
|
229
|
-
if (
|
|
240
|
+
if (settings.hardware_offload !== undefined) found.push(`${where}.hardware_offload`);
|
|
241
|
+
if (kind === "loopback" && settings.ipv6_ra) found.push(`${where}.ipv6_ra, on a loopback`);
|
|
242
|
+
if (kind === "loopback" && settings.mtu !== undefined) found.push(`${where}.mtu, on a loopback`);
|
|
243
|
+
if (kind === "gre" && settings.vrrp?.length) found.push(`${where}.vrrp, on a tunnel`);
|
|
230
244
|
|
|
231
245
|
// A VLAN interface sends no larger packets than the ports carrying its VLAN take; the device warns and carries on.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
246
|
+
const vlanInterface = kind === "vlan_interface" ? device.interfaces?.vlan_interface?.[name] : undefined;
|
|
247
|
+
if (vlanInterface?.mtu !== undefined) {
|
|
248
|
+
const smallest = Math.min(...Junos.carriers(device, vlanInterface.vlan));
|
|
249
|
+
if (vlanInterface.mtu > smallest)
|
|
250
|
+
found.push(`${where}.mtu ${vlanInterface.mtu}, above the ${smallest} its VLAN's ports carry`);
|
|
235
251
|
}
|
|
236
252
|
|
|
237
|
-
found.push(...Junos.routedGaps(
|
|
253
|
+
found.push(...Junos.routedGaps(where, settings));
|
|
238
254
|
|
|
239
|
-
|
|
255
|
+
const lag = kind === "lag" ? device.interfaces?.lag?.[name] : undefined;
|
|
256
|
+
if (lag) found.push(...Junos.switchedGaps(where, lag, device));
|
|
240
257
|
|
|
241
|
-
if (
|
|
242
|
-
const table =
|
|
243
|
-
if (loopbackTables.has(table)) found.push(
|
|
258
|
+
if (kind === "loopback") {
|
|
259
|
+
const table = settings.vrf ?? "";
|
|
260
|
+
if (loopbackTables.has(table)) found.push(`${where}, a second loopback in one routing table`);
|
|
244
261
|
loopbackTables.add(table);
|
|
245
|
-
if (
|
|
262
|
+
if (settings.vrrp?.length) found.push(`${where}.vrrp, on a loopback`);
|
|
246
263
|
}
|
|
247
264
|
}
|
|
248
265
|
|
|
249
266
|
const ids = [...Derive.lagIds(device).values()];
|
|
250
|
-
if (ids.length !== new Set(ids).size) found.push("interfaces, two LAGs with one number");
|
|
267
|
+
if (ids.length !== new Set(ids).size) found.push("interfaces.lag, two LAGs with one number");
|
|
251
268
|
|
|
252
|
-
|
|
269
|
+
const stp = device.switching?.stp;
|
|
270
|
+
if (stp?.priority !== undefined && stp.priority % 4096 !== 0) found.push("switching.stp.priority, in steps of 4096");
|
|
253
271
|
|
|
254
272
|
const routing = device.routing;
|
|
255
273
|
found.push(...Junos.bgpGaps(device));
|
|
256
274
|
|
|
257
275
|
const tables = new Map<string, number>();
|
|
258
276
|
|
|
259
|
-
for (const route of routing?.
|
|
260
|
-
if (route.description) found.push(`routing.
|
|
277
|
+
for (const route of routing?.static_routes ?? []) {
|
|
278
|
+
if (route.description) found.push(`routing.static_routes ${route.prefix} description`);
|
|
261
279
|
|
|
262
280
|
const table = `${route.vrf ?? ""} ${Address.split(route.prefix).addr}/${Address.split(route.prefix).len}`;
|
|
263
281
|
tables.set(table, (tables.get(table) ?? 0) + 1);
|
|
264
282
|
}
|
|
265
283
|
|
|
266
|
-
for (const route of routing?.
|
|
284
|
+
for (const route of routing?.static_routes ?? []) {
|
|
267
285
|
const table = `${route.vrf ?? ""} ${Address.split(route.prefix).addr}/${Address.split(route.prefix).len}`;
|
|
268
286
|
if (route.blackhole && tables.get(table)! > 1)
|
|
269
|
-
found.push(`routing.
|
|
287
|
+
found.push(`routing.static_routes ${route.prefix}, a blackhole beside another route to the same prefix`);
|
|
270
288
|
}
|
|
271
289
|
|
|
272
290
|
for (const area of Object.values(routing?.ospf?.areas ?? {})) {
|
|
273
|
-
for (const iface of Object.
|
|
274
|
-
|
|
275
|
-
if (settings?.vrf) found.push(`routing.ospf on ${iface}, which routes in a VRF`);
|
|
291
|
+
for (const [iface, settings] of Object.entries(area.interfaces)) {
|
|
292
|
+
if (settings && Derive.settings(device, iface)?.vrf) found.push(`routing.ospf on ${iface}, which routes in a VRF`);
|
|
276
293
|
}
|
|
277
294
|
}
|
|
278
295
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
296
|
+
if (device.firewall !== undefined) found.push("firewall");
|
|
297
|
+
if (system.hardware_offload !== undefined) found.push("system.hardware_offload");
|
|
298
|
+
if (system.certificates !== undefined) found.push("system.certificates");
|
|
282
299
|
|
|
283
|
-
const
|
|
300
|
+
const services = device.services ?? {};
|
|
301
|
+
const relayed = new Set(Object.values(services.dhcp_relay ?? {}).map((relay) => relay.interface));
|
|
284
302
|
|
|
285
|
-
for (const [name, server] of Object.entries(
|
|
303
|
+
for (const [name, server] of Object.entries(services.dhcp_server ?? {})) {
|
|
286
304
|
// The device refuses a relay and a server on one interface: `Interface irb.10 already configured`.
|
|
287
|
-
if (relayed.has(server.interface)) found.push(`
|
|
305
|
+
if (relayed.has(server.interface)) found.push(`services.dhcp_server.${name}.interface, which a DHCP relay also uses`);
|
|
288
306
|
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
if (Address.family(Address.host(server.network)) === "ipv6") found.push(`dhcp.${name}.network, IPv6`);
|
|
307
|
+
if (Derive.settings(device, server.interface)?.vrf) found.push(`services.dhcp_server.${name}.interface, which routes in a VRF`);
|
|
308
|
+
if (Address.family(Address.host(server.network)) === "ipv6") found.push(`services.dhcp_server.${name}.network, IPv6`);
|
|
292
309
|
}
|
|
293
310
|
|
|
294
|
-
for (const [name, relay] of Object.entries(
|
|
295
|
-
if (relay.servers.some((server) => Address.family(server) === "ipv6")) found.push(`dhcp_relay.${name}.servers, IPv6`);
|
|
311
|
+
for (const [name, relay] of Object.entries(services.dhcp_relay ?? {})) {
|
|
312
|
+
if (relay.servers.some((server) => Address.family(server) === "ipv6")) found.push(`services.dhcp_relay.${name}.servers, IPv6`);
|
|
296
313
|
}
|
|
297
314
|
|
|
298
|
-
const flow = device.flow_export;
|
|
299
|
-
if (flow && flow.protocol !== "sflow") found.push(`flow_export.protocol ${flow.protocol}, the platform exports sFlow`);
|
|
300
|
-
if (flow?.protocol === "sflow" && flow.sampling === undefined) found.push("flow_export.sampling, the platform samples");
|
|
315
|
+
const flow = device.monitoring?.flow_export;
|
|
316
|
+
if (flow && flow.protocol !== "sflow") found.push(`monitoring.flow_export.protocol ${flow.protocol}, the platform exports sFlow`);
|
|
317
|
+
if (flow?.protocol === "sflow" && flow.sampling === undefined) found.push("monitoring.flow_export.sampling, the platform samples");
|
|
301
318
|
|
|
302
319
|
for (const name of flow?.interfaces ?? []) {
|
|
303
|
-
if (names.members(name).some((port) => port.startsWith("em")))
|
|
320
|
+
if (names.members(name).some((port) => port.startsWith("em")))
|
|
321
|
+
found.push(`monitoring.flow_export.interfaces ${name}, a management port`);
|
|
304
322
|
}
|
|
305
323
|
|
|
306
324
|
found.push(...Derive.unproved(device, Junos.PROVED));
|
|
@@ -749,7 +767,7 @@ export class Junos extends Platform {
|
|
|
749
767
|
|
|
750
768
|
// The CLI is opened on a prompt asked for after the login, since the login's own prompt is already read.
|
|
751
769
|
const login = async () => {
|
|
752
|
-
await SerialConsole.login(opened.channel, Junos.CONSOLE, user, password, console.
|
|
770
|
+
await SerialConsole.login(opened.channel, Junos.CONSOLE, user, password, console.password_only);
|
|
753
771
|
const ready = Junos.openCli(opened.channel);
|
|
754
772
|
opened.channel.write("\n");
|
|
755
773
|
await ready;
|
|
@@ -912,7 +930,10 @@ export class Junos extends Platform {
|
|
|
912
930
|
/** The IP MTU of every port and LAG that carries a VLAN; 1500 where none is given. */
|
|
913
931
|
private static carriers(device: Device, vlan: string): number[] {
|
|
914
932
|
const sizes = [1500];
|
|
915
|
-
const carrying
|
|
933
|
+
const carrying: (Port | Lag | undefined)[] = [
|
|
934
|
+
...Derive.ports(device).map(([, settings]) => settings),
|
|
935
|
+
...Object.values(device.interfaces?.lag ?? {}),
|
|
936
|
+
];
|
|
916
937
|
|
|
917
938
|
for (const settings of carrying) {
|
|
918
939
|
if (!settings || !("access_vlan" in settings || "trunk_vlans" in settings)) continue;
|
|
@@ -929,12 +950,16 @@ export class Junos extends Platform {
|
|
|
929
950
|
const found: string[] = [];
|
|
930
951
|
const bgp = device.routing?.bgp;
|
|
931
952
|
|
|
953
|
+
if (bgp && bgp.asn === undefined) found.push("routing.bgp.asn, absent: write it here or in circuit.ts");
|
|
954
|
+
|
|
932
955
|
for (const [name, neighbor] of Object.entries(bgp?.neighbors ?? {})) {
|
|
956
|
+
if (!neighbor) continue;
|
|
957
|
+
|
|
933
958
|
const settings = JunosBgp.settingsOf(device, neighbor);
|
|
934
959
|
if (settings.role) found.push(`routing.bgp.neighbors.${name}.role, Junos 21 has no BGP roles`);
|
|
935
960
|
// IPv4 routes over an IPv6 session need a local IPv4 address the config has no field for.
|
|
936
|
-
if (Address.family(neighbor.address) === "ipv6" && settings.
|
|
937
|
-
found.push(`routing.bgp.neighbors.${name}.
|
|
961
|
+
if (Address.family(neighbor.address) === "ipv6" && settings.address_families?.includes("ipv4")) {
|
|
962
|
+
found.push(`routing.bgp.neighbors.${name}.address_families ipv4 over an IPv6 session`);
|
|
938
963
|
}
|
|
939
964
|
|
|
940
965
|
if (settings.keepalive !== undefined)
|
|
@@ -945,22 +970,30 @@ export class Junos extends Platform {
|
|
|
945
970
|
if (server.description) found.push(`routing.rpki ${server.address} description`);
|
|
946
971
|
}
|
|
947
972
|
|
|
948
|
-
for (const [name, rules] of Object.entries(device.policies ?? {})) {
|
|
949
|
-
rules.forEach((rule, index) => {
|
|
950
|
-
const where = `policies.${name}[${index}]`;
|
|
973
|
+
for (const [name, rules] of Object.entries(device.routing?.policies ?? {})) {
|
|
974
|
+
(rules ?? []).forEach((rule, index) => {
|
|
975
|
+
const where = `routing.policies.${name}[${index}]`;
|
|
976
|
+
if ("communities" in rule && rule.communities !== undefined)
|
|
977
|
+
found.push(`${where}.communities, a rule the platform does not write`);
|
|
978
|
+
if (!JunosBgp.isRouteRule(rule)) return;
|
|
979
|
+
|
|
951
980
|
if (rule.set?.preferred_source) found.push(`${where}.set.preferred_source`);
|
|
952
|
-
if (rule.
|
|
981
|
+
if (rule.if?.prefix && rule.if.prefix_length) found.push(`${where}.if.prefix with prefix_length`);
|
|
953
982
|
});
|
|
954
983
|
}
|
|
955
984
|
|
|
985
|
+
// A rule takes its family from its prefix set, so a set holds one.
|
|
986
|
+
for (const [name, set] of Object.entries(device.routing?.prefix_sets ?? {})) {
|
|
987
|
+
const families = new Set(JunosBgp.prefixesOf(set ?? { prefixes: [] }).map((prefix) => Address.family(Address.host(prefix))));
|
|
988
|
+
if (families.size > 1)
|
|
989
|
+
found.push(`routing.prefix_sets.${name}.prefixes of both families, a policy rule takes its family from them`);
|
|
990
|
+
}
|
|
991
|
+
|
|
956
992
|
return found;
|
|
957
993
|
}
|
|
958
994
|
|
|
959
995
|
/** Layer 3 settings the platform cannot express on a port or an interface. */
|
|
960
|
-
private static routedGaps(
|
|
961
|
-
where: string,
|
|
962
|
-
settings: { addresses?: readonly (string | { address: string; peer: string })[]; vrrp?: readonly { id: number; address: IP }[] }
|
|
963
|
-
): string[] {
|
|
996
|
+
private static routedGaps(where: string, settings: Pick<Routed, "addresses" | "vrrp">): string[] {
|
|
964
997
|
const found: string[] = [];
|
|
965
998
|
const addresses = (settings.addresses ?? []).map((entry) => (typeof entry === "string" ? entry : entry.address));
|
|
966
999
|
|
|
@@ -985,11 +1018,11 @@ export class Junos extends Platform {
|
|
|
985
1018
|
found.push(`${where}.storm_control, one threshold for every kind`);
|
|
986
1019
|
}
|
|
987
1020
|
|
|
988
|
-
if (typeof settings.stp === "object" && !device.stp) found.push(`${where}.stp while spanning tree is off on the device`);
|
|
1021
|
+
if (typeof settings.stp === "object" && !device.switching?.stp) found.push(`${where}.stp while spanning tree is off on the device`);
|
|
989
1022
|
|
|
990
|
-
// The limit and the filter sit on the switching family. The filter and an
|
|
1023
|
+
// The limit and the filter sit on the switching family. The filter and an ACL would both be its input filter.
|
|
991
1024
|
const switched = settings.access_vlan || settings.trunk_vlans;
|
|
992
|
-
const inLag = "
|
|
1025
|
+
const inLag = "in_lag" in settings && settings.in_lag;
|
|
993
1026
|
|
|
994
1027
|
for (const field of ["mac_limit", "ethertypes"] as const) {
|
|
995
1028
|
if (settings[field] === undefined) continue;
|
|
@@ -998,8 +1031,8 @@ export class Junos extends Platform {
|
|
|
998
1031
|
else if (!switched) found.push(`${where}.${field}, on a port or LAG that switches no VLAN`);
|
|
999
1032
|
}
|
|
1000
1033
|
|
|
1001
|
-
if (settings.ethertypes && "
|
|
1002
|
-
found.push(`${where}.ethertypes with an
|
|
1034
|
+
if (settings.ethertypes && "ingress_acl" in settings && settings.ingress_acl)
|
|
1035
|
+
found.push(`${where}.ethertypes with an ingress_acl, the port takes one filter`);
|
|
1003
1036
|
|
|
1004
1037
|
return found;
|
|
1005
1038
|
}
|