@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
|
@@ -22,7 +22,7 @@ Behaviour that looks like something else. Each one has been hit on a CE6855 runn
|
|
|
22
22
|
- `vlan batch` has every VLAN on one line, and undoing a list that includes a VLAN with an L3 interface is refused. A change sends only the VLANs added and `undo vlan batch` for the ones removed.
|
|
23
23
|
- A VLAN name is unique across VLANs: a new VLAN cannot take a name another still has. VLANs no longer used, and their VLAN interfaces, are removed first, in the same candidate.
|
|
24
24
|
- `undo ssh server key-exchange <algorithms>` is refused with "Too many parameters"; `undo ssh server key-exchange` takes none. `hmac` was proved the same way. `cipher` is undone the same way but was not proved on its own.
|
|
25
|
-
- A console on a password only (`
|
|
25
|
+
- A console on a password only (`password_only`) shows `Please Press ENTER.` after a logout or a refused password, and `Error: The password is invalid.` for a wrong one.
|
|
26
26
|
- Once SNMP is on, the switch prints an engine ID of its own, `snmp-agent local-engineid`, derived from its MAC, and prints each SNMPv3 user as a bare `snmp-agent usm-user v3 <name>` line before its settings. Planning against either as unrendered would undo the user; seen on a CE6855 on V200R005C00SPC800.
|
|
27
27
|
- `apply --secrets` sends every line holding a secret again, the local users' passwords included, and a password among the user's last ten is refused, which discards the whole candidate. Send it only when a password changed.
|
|
28
28
|
- An SNMPv3 key prints as `cipher %^%#...%^%#`, which can be turned back into the key. The read redacts every value after `cipher`, as it does a password.
|
|
@@ -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 } from "../../
|
|
9
|
+
import type { Device, Lag, VlanInterface } 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";
|
|
@@ -106,46 +106,53 @@ export class Vrp extends Platform {
|
|
|
106
106
|
|
|
107
107
|
if (device.management?.interfaces) found.push("management.interfaces, which only FortiOS builds for now");
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
if (iface.type === "bridge") found.push(`interfaces.${name}, a bridge, which only FortiOS builds for now`);
|
|
111
|
-
if (iface.type === "vlan" && iface.mac) found.push(`interfaces.${name}.mac, which only RouterOS sets for now`);
|
|
112
|
-
}
|
|
109
|
+
const interfaces = Derive.interfaces(device);
|
|
113
110
|
|
|
114
|
-
for (const
|
|
115
|
-
if (
|
|
111
|
+
for (const { name, kind, settings } of interfaces) {
|
|
112
|
+
if (kind === "bridge") found.push(`interfaces.bridge.${name}, a bridge, which only FortiOS builds for now`);
|
|
113
|
+
if (kind === "vlan_interface" && (settings as VlanInterface).mac)
|
|
114
|
+
found.push(`interfaces.vlan_interface.${name}.mac, which only RouterOS sets for now`);
|
|
115
|
+
}
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
for (const [name, port] of Derive.ports(device)) {
|
|
118
|
+
for (const field of ["speed", "ingress_acl", "mtu", "addresses", "vrrp", "vrf", "ipv6_ra", "hardware_offload"] as const) {
|
|
119
|
+
if (port[field] !== undefined) found.push(`interfaces.ethernet.${name}.${field}, ports are switched; use a VLAN interface`);
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
for (const field of ["mac_limit", "ethertypes"] as const) {
|
|
122
|
-
if (port[field] !== undefined && port.
|
|
123
|
+
if (port[field] !== undefined && port.in_lag)
|
|
124
|
+
found.push(`interfaces.ethernet.${name}.${field} on a LAG member, set it on the LAG`);
|
|
123
125
|
}
|
|
124
126
|
|
|
125
|
-
if (port.lldp === true && !device.lldp)
|
|
127
|
+
if (port.lldp === true && !device.switching?.lldp)
|
|
128
|
+
found.push(`interfaces.ethernet.${name}.lldp true while LLDP is off on the device`);
|
|
126
129
|
const storm = Object.values(port.storm_control ?? {});
|
|
127
|
-
if (storm.some((threshold) => "pps" in threshold))
|
|
130
|
+
if (storm.some((threshold) => "pps" in threshold))
|
|
131
|
+
found.push(`interfaces.ethernet.${name}.storm_control in pps, the platform takes percent`);
|
|
128
132
|
}
|
|
129
133
|
|
|
130
|
-
for (const
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
if (
|
|
134
|
-
if (
|
|
135
|
-
if (
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
for (const { name, kind, settings } of interfaces) {
|
|
135
|
+
const where = `interfaces.${kind}.${name}`;
|
|
136
|
+
if (kind !== "vlan_interface" && kind !== "lag") found.push(`${where}, a ${kind}`);
|
|
137
|
+
if (kind === "lag" && settings.addresses?.length) found.push(`${where}.addresses, LAGs are switched`);
|
|
138
|
+
if (kind === "lag" && (settings as Lag).mac_limit !== undefined) found.push(`${where}.mac_limit, on a LAG is not proved`);
|
|
139
|
+
if (kind === "lag" && (settings as Lag).ethertypes) found.push(`${where}.ethertypes, on a LAG is not proved`);
|
|
140
|
+
if (settings.mtu !== undefined) found.push(`${where}.mtu`);
|
|
141
|
+
if (settings.ipv6_ra) found.push(`${where}.ipv6_ra`);
|
|
142
|
+
if (settings.hardware_offload !== undefined) found.push(`${where}.hardware_offload, the switch chip forwards everything`);
|
|
143
|
+
for (const group of settings.vrrp ?? []) if (Address.family(group.address) === "ipv6") found.push(`${where}.vrrp for IPv6`);
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
const routing = device.routing;
|
|
147
|
+
|
|
148
|
+
for (const [name, vrf] of Object.entries(routing?.vrfs ?? {})) {
|
|
149
|
+
if (vrf?.description) found.push(`routing.vrfs.${name}.description`);
|
|
143
150
|
}
|
|
144
151
|
|
|
145
|
-
const routing = device.routing;
|
|
146
152
|
if (routing?.bgp) found.push("routing.bgp");
|
|
147
153
|
if (routing?.rpki) found.push("routing.rpki");
|
|
148
|
-
if (routing?.ospf?.
|
|
154
|
+
if (routing?.ospf?.address_families?.includes("ipv6")) found.push("routing.ospf for IPv6");
|
|
155
|
+
if (routing?.router_id && !routing.ospf) found.push("routing.router_id without routing.ospf, only OSPF uses it here");
|
|
149
156
|
|
|
150
157
|
const bfd = routing?.bfd;
|
|
151
158
|
if (bfd && !(Number.isInteger(bfd.interval) && bfd.interval >= 50 && bfd.interval <= 1000))
|
|
@@ -153,46 +160,59 @@ export class Vrp extends Platform {
|
|
|
153
160
|
if (bfd && !(Number.isInteger(bfd.multiplier) && bfd.multiplier >= 3 && bfd.multiplier <= 50))
|
|
154
161
|
found.push("routing.bfd.multiplier, between 3 and 50 on the platform");
|
|
155
162
|
|
|
156
|
-
for (const route of routing?.
|
|
163
|
+
for (const route of routing?.static_routes ?? []) {
|
|
157
164
|
if (!route.bfd) continue;
|
|
158
165
|
|
|
159
|
-
const where = `routing.
|
|
166
|
+
const where = `routing.static_routes ${route.prefix} bfd`;
|
|
167
|
+
const nextHop = route.next_hop && Address.host(route.next_hop);
|
|
160
168
|
if (route.vrf) found.push(`${where} in a VRF, not proved`);
|
|
161
|
-
else if (!
|
|
162
|
-
else if (!VrpRender.localAddressTo(device,
|
|
163
|
-
found.push(`${where}, the device has no address on the network of ${
|
|
169
|
+
else if (!nextHop || Address.family(nextHop) !== "ipv4") found.push(`${where}, needs an IPv4 next_hop`);
|
|
170
|
+
else if (!VrpRender.localAddressTo(device, nextHop))
|
|
171
|
+
found.push(`${where}, the device has no address on the network of ${nextHop}`);
|
|
164
172
|
}
|
|
165
173
|
|
|
166
|
-
for (const [index, route] of (routing?.
|
|
174
|
+
for (const [index, route] of (routing?.static_routes ?? []).entries()) {
|
|
167
175
|
if (route.description && Address.family(Address.split(route.prefix).addr) === "ipv6")
|
|
168
|
-
found.push(`routing.
|
|
176
|
+
found.push(`routing.static_routes[${index}].description for IPv6`);
|
|
169
177
|
}
|
|
170
178
|
|
|
171
|
-
|
|
172
|
-
|
|
179
|
+
const refusedGroups = [
|
|
180
|
+
["firewall", device.firewall],
|
|
181
|
+
["services.dhcp_server", device.services?.dhcp_server],
|
|
182
|
+
["switching.acls", device.switching?.acls],
|
|
183
|
+
["routing.policies", routing?.policies],
|
|
184
|
+
["routing.prefix_sets", routing?.prefix_sets],
|
|
185
|
+
["system.hardware_offload", device.system?.hardware_offload],
|
|
186
|
+
["system.certificates", device.system?.certificates],
|
|
187
|
+
] as const;
|
|
188
|
+
|
|
189
|
+
for (const [path, value] of refusedGroups) {
|
|
190
|
+
if (value !== undefined) found.push(path);
|
|
173
191
|
}
|
|
174
192
|
|
|
175
|
-
for (const [name, relay] of Object.entries(device.dhcp_relay ?? {})) {
|
|
176
|
-
if (device.interfaces?.[relay.interface]
|
|
177
|
-
found.push(`dhcp_relay.${name}.interface, relays run on VLAN interfaces`);
|
|
193
|
+
for (const [name, relay] of Object.entries(device.services?.dhcp_relay ?? {})) {
|
|
194
|
+
if (!device.interfaces?.vlan_interface?.[relay.interface])
|
|
195
|
+
found.push(`services.dhcp_relay.${name}.interface, relays run on VLAN interfaces`);
|
|
178
196
|
}
|
|
179
197
|
|
|
180
198
|
// The platform reserves this name.
|
|
181
|
-
if (device.users
|
|
199
|
+
if (device.system?.users?.admin) found.push("system.users.admin, the platform reserves the name");
|
|
182
200
|
|
|
183
201
|
const management = device.management ?? {};
|
|
184
202
|
|
|
185
|
-
for (const field of ["http", "https", "api", "api_tls", "ftp", "
|
|
203
|
+
for (const field of ["http", "https", "api", "api_tls", "ftp", "native_protocol", "privilege_password"] as const) {
|
|
186
204
|
if (management[field]) found.push(`management.${field}`);
|
|
187
205
|
}
|
|
188
206
|
|
|
189
207
|
if (management.ssh?.auth_timeout || management.ssh?.auth_retries) found.push("management.ssh.auth_timeout and auth_retries");
|
|
190
208
|
|
|
191
|
-
|
|
209
|
+
const lldp = device.switching?.lldp;
|
|
210
|
+
if (lldp && lldp !== true) found.push("switching.lldp.interfaces, use lldp on the ports instead");
|
|
192
211
|
|
|
193
212
|
const system = device.system ?? {};
|
|
194
213
|
|
|
195
214
|
if (system.dns || system.release_channel || system.banner) found.push("system.dns, release_channel and banner");
|
|
215
|
+
if (device.services?.dns_server) found.push("services.dns_server");
|
|
196
216
|
|
|
197
217
|
const local = system.logging?.local;
|
|
198
218
|
if (local?.lines_per_file !== undefined)
|
|
@@ -200,27 +220,31 @@ export class Vrp extends Platform {
|
|
|
200
220
|
if (local?.files !== undefined && !(Number.isInteger(local.files) && local.files >= 3 && local.files <= 500))
|
|
201
221
|
found.push("system.logging.local.files, between 3 and 500 on the platform");
|
|
202
222
|
|
|
203
|
-
|
|
204
|
-
|
|
223
|
+
const monitoring = device.monitoring ?? {};
|
|
224
|
+
|
|
225
|
+
for (const collector of monitoring.syslog ?? []) {
|
|
226
|
+
if (Address.family(collector.address) === "ipv6") found.push(`monitoring.syslog ${collector.address}, IPv6 is not proved`);
|
|
205
227
|
}
|
|
206
228
|
|
|
207
|
-
for (const source of
|
|
208
|
-
if (Address.family(Address.split(source).addr) === "ipv6") found.push(`
|
|
229
|
+
for (const source of monitoring.snmp?.allow ?? []) {
|
|
230
|
+
if (Address.family(Address.split(source).addr) === "ipv6") found.push(`monitoring.snmp.allow ${source}, IPv6 is not proved`);
|
|
209
231
|
}
|
|
210
232
|
|
|
211
|
-
if (
|
|
233
|
+
if (monitoring.transceivers !== undefined) found.push("monitoring.transceivers");
|
|
212
234
|
|
|
213
235
|
// SNMP takes sources of its own here, in an ACL of its own.
|
|
214
|
-
found.push(...Derive.switchUnsupported(device
|
|
236
|
+
found.push(...Derive.switchUnsupported(device, ["snmp"]));
|
|
215
237
|
found.push(...Derive.unproved(device, ["alarm-severity", "bfd-timers", "static-route-bfd", "mac-limit", "ethertype-filter"]));
|
|
216
238
|
|
|
217
|
-
const flow =
|
|
218
|
-
if (flow && flow.protocol !== "sflow") found.push(`flow_export.protocol ${flow.protocol}, the platform exports sFlow`);
|
|
239
|
+
const flow = monitoring.flow_export;
|
|
240
|
+
if (flow && flow.protocol !== "sflow") found.push(`monitoring.flow_export.protocol ${flow.protocol}, the platform exports sFlow`);
|
|
219
241
|
const sampling = flow?.protocol === "sflow" ? flow.sampling : undefined;
|
|
220
242
|
const samplingInRange = sampling !== undefined && Number.isInteger(sampling) && sampling >= 4096 && sampling <= 4_294_967_295;
|
|
221
243
|
|
|
222
244
|
if (flow?.protocol === "sflow" && !samplingInRange) {
|
|
223
|
-
found.push(
|
|
245
|
+
found.push(
|
|
246
|
+
"monitoring.flow_export.sampling, required and between 4096 and 4294967295: the platform samples, one packet in 4096 at most"
|
|
247
|
+
);
|
|
224
248
|
}
|
|
225
249
|
|
|
226
250
|
return found;
|
|
@@ -565,7 +589,7 @@ export class Vrp extends Platform {
|
|
|
565
589
|
const opened = await SerialConsole.open(console);
|
|
566
590
|
|
|
567
591
|
const login = async () => {
|
|
568
|
-
await SerialConsole.login(opened.channel, Vrp.CONSOLE, user, password, console.
|
|
592
|
+
await SerialConsole.login(opened.channel, Vrp.CONSOLE, user, password, console.password_only);
|
|
569
593
|
await Ssh.send(opened.channel, "screen-length 0 temporary", 10_000, 1500);
|
|
570
594
|
};
|
|
571
595
|
|
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
import { Address } from "../../core/address";
|
|
4
4
|
import { Secrets } from "../../core/secrets";
|
|
5
5
|
import { Utils } from "../../core/utils";
|
|
6
|
-
import type { Device,
|
|
6
|
+
import type { Device, Lag, Port, VlanInterface, Vrrp } from "../../model";
|
|
7
7
|
import { Catalog } from "../catalog";
|
|
8
8
|
import { Derive } from "../derive";
|
|
9
9
|
|
|
10
|
-
type AnyPort = Port<string, string, string, string>;
|
|
11
|
-
type AnyInterface = Interface<string, string>;
|
|
12
|
-
type LagInterface = Extract<AnyInterface, { type: "lag" }>;
|
|
13
|
-
|
|
14
10
|
/** Builds the configuration as blocks: a header line, its indented lines, then `#`. */
|
|
15
11
|
export class VrpRender {
|
|
16
12
|
/** The ACL that guards every management service. */
|
|
@@ -66,19 +62,21 @@ export class VrpRender {
|
|
|
66
62
|
private static readonly ETHERTYPE = { ipv4: "ip", ipv6: "ipv6", arp: "arp" } as const;
|
|
67
63
|
|
|
68
64
|
private readonly lines: string[] = [];
|
|
69
|
-
private readonly vlans: NonNullable<Device["vlans"]>;
|
|
70
|
-
private readonly
|
|
65
|
+
private readonly vlans: NonNullable<NonNullable<Device["switching"]>["vlans"]>;
|
|
66
|
+
private readonly lags: NonNullable<NonNullable<Device["interfaces"]>["lag"]>;
|
|
67
|
+
private readonly vlanInterfaces: NonNullable<NonNullable<Device["interfaces"]>["vlan_interface"]>;
|
|
71
68
|
private readonly lagNumbers: Map<string, number>;
|
|
72
69
|
/** The layer 2 ACL of each set of ethertypes the ports take, by the set written as `ipv4 ipv6 arp`. */
|
|
73
70
|
private readonly ethertypeAcls = new Map<string, number>();
|
|
74
71
|
|
|
75
72
|
private constructor(private readonly device: Device) {
|
|
76
|
-
this.vlans = device.vlans ?? {};
|
|
77
|
-
this.
|
|
73
|
+
this.vlans = device.switching?.vlans ?? {};
|
|
74
|
+
this.lags = device.interfaces?.lag ?? {};
|
|
75
|
+
this.vlanInterfaces = device.interfaces?.vlan_interface ?? {};
|
|
78
76
|
this.lagNumbers = Derive.lagIds(device);
|
|
79
77
|
|
|
80
|
-
for (const port of
|
|
81
|
-
const key = port
|
|
78
|
+
for (const [, port] of Derive.ports(device)) {
|
|
79
|
+
const key = port.ethertypes?.join(" ");
|
|
82
80
|
if (key && !this.ethertypeAcls.has(key)) this.ethertypeAcls.set(key, VrpRender.FIRST_L2_ACL + this.ethertypeAcls.size);
|
|
83
81
|
}
|
|
84
82
|
}
|
|
@@ -89,6 +87,13 @@ export class VrpRender {
|
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
/** `99 999 to 1000 2000`, the platform's compression of a VLAN list. */
|
|
90
|
+
/** `management.allow` as a list. The project turns an address set named there into its addresses before rendering. */
|
|
91
|
+
static managementAllow(device: Device): readonly string[] {
|
|
92
|
+
const allow = device.management?.allow ?? [];
|
|
93
|
+
if (typeof allow === "string") throw new Error(`management.allow names the address set ${allow}, not its addresses`);
|
|
94
|
+
return allow;
|
|
95
|
+
}
|
|
96
|
+
|
|
92
97
|
static vlanBatch(ids: number[]): string {
|
|
93
98
|
const sorted = [...new Set(ids)].sort((a, b) => a - b);
|
|
94
99
|
const ranges: string[] = [];
|
|
@@ -114,23 +119,23 @@ export class VrpRender {
|
|
|
114
119
|
this.users();
|
|
115
120
|
this.vrfs();
|
|
116
121
|
|
|
117
|
-
for (const [name, lag] of Object.entries(this.
|
|
118
|
-
if (lag
|
|
122
|
+
for (const [name, lag] of Object.entries(this.lags)) {
|
|
123
|
+
if (lag) this.block(`interface ${this.nameOf(name)}`, [...VrpRender.lagMode(lag), ...this.switching(lag)]);
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
for (const [name, iface] of Object.entries(this.
|
|
122
|
-
if (iface
|
|
126
|
+
for (const [name, iface] of Object.entries(this.vlanInterfaces)) {
|
|
127
|
+
if (iface) this.block(`interface ${this.nameOf(name)}`, this.vlanInterface(name, iface));
|
|
123
128
|
}
|
|
124
129
|
|
|
125
130
|
for (const port of Catalog.ports(device.platform, device.model)) {
|
|
126
|
-
|
|
127
|
-
this.block(`interface ${this.nameOf(port)}`, this.physicalPort(port, settings));
|
|
131
|
+
this.block(`interface ${this.nameOf(port)}`, this.physicalPort(port, device.interfaces?.ethernet?.[port]));
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
this.staticRoutes();
|
|
131
135
|
this.ospf();
|
|
132
136
|
if (this.wantsBfd()) this.block("bfd", []);
|
|
133
|
-
|
|
137
|
+
const relays = device.services?.dhcp_relay;
|
|
138
|
+
if (relays && Object.keys(relays).length) {
|
|
134
139
|
this.line("dhcp enable");
|
|
135
140
|
this.separator();
|
|
136
141
|
}
|
|
@@ -167,10 +172,10 @@ export class VrpRender {
|
|
|
167
172
|
|
|
168
173
|
/** What the platform calls a port, a LAG or a VLAN interface. */
|
|
169
174
|
private nameOf(portOrInterface: string): string {
|
|
170
|
-
|
|
171
|
-
if (iface?.type === "lag") return `Eth-Trunk${this.lagNumbers.get(portOrInterface)}`;
|
|
175
|
+
if (this.lags[portOrInterface]) return `Eth-Trunk${this.lagNumbers.get(portOrInterface)}`;
|
|
172
176
|
|
|
173
|
-
|
|
177
|
+
const vlanInterface = this.vlanInterfaces[portOrInterface];
|
|
178
|
+
if (vlanInterface) return `Vlanif${this.vlanId(vlanInterface.vlan)}`;
|
|
174
179
|
|
|
175
180
|
return Catalog.spell(this.device.platform, this.device.model, portOrInterface);
|
|
176
181
|
}
|
|
@@ -192,7 +197,7 @@ export class VrpRender {
|
|
|
192
197
|
this.separator();
|
|
193
198
|
}
|
|
194
199
|
|
|
195
|
-
const stp = device.stp;
|
|
200
|
+
const stp = device.switching?.stp;
|
|
196
201
|
if (!stp) this.line("stp disable");
|
|
197
202
|
else {
|
|
198
203
|
// MSTP is the platform's default and is not written.
|
|
@@ -200,19 +205,19 @@ export class VrpRender {
|
|
|
200
205
|
if (stp.priority !== undefined) this.line(`stp priority ${stp.priority}`);
|
|
201
206
|
}
|
|
202
207
|
// LLDP is off by default and turned on by the config.
|
|
203
|
-
if (device.lldp) this.line("lldp enable");
|
|
208
|
+
if (device.switching?.lldp) this.line("lldp enable");
|
|
204
209
|
this.separator();
|
|
205
210
|
|
|
206
|
-
|
|
207
|
-
if (!ntp?.serve) {
|
|
211
|
+
if (!device.services?.ntp_server) {
|
|
208
212
|
this.line("ntp server disable");
|
|
209
213
|
this.line("ntp ipv6 server disable");
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
// A server written with its length is written as the address alone.
|
|
217
|
+
for (const server of device.system?.ntp?.servers ?? []) this.line(`ntp unicast-server ${Address.host(server)}`);
|
|
213
218
|
this.separator();
|
|
214
219
|
|
|
215
|
-
const ids = Object.values(
|
|
220
|
+
const ids = Object.values(this.vlans).flatMap((vlan) => (vlan ? [vlan.id] : []));
|
|
216
221
|
if (ids.length) {
|
|
217
222
|
this.line(`vlan batch ${VrpRender.vlanBatch(ids)}`);
|
|
218
223
|
this.separator();
|
|
@@ -226,14 +231,15 @@ export class VrpRender {
|
|
|
226
231
|
}
|
|
227
232
|
|
|
228
233
|
private vlanBlocks(): void {
|
|
229
|
-
for (const [name, vlan] of Object.entries(this.
|
|
234
|
+
for (const [name, vlan] of Object.entries(this.vlans)) {
|
|
235
|
+
if (!vlan) continue;
|
|
230
236
|
this.block(`vlan ${vlan.id}`, [`name ${name}`, ...(vlan.description ? [`description ${vlan.description}`] : [])]);
|
|
231
237
|
}
|
|
232
238
|
}
|
|
233
239
|
|
|
234
240
|
/** One ACL guards every management service: a permit per source, then an explicit deny. */
|
|
235
241
|
private managementAcl(): void {
|
|
236
|
-
const allow = this.device
|
|
242
|
+
const allow = VrpRender.managementAllow(this.device);
|
|
237
243
|
if (!allow.length) return;
|
|
238
244
|
|
|
239
245
|
this.block(`acl number ${VrpRender.MANAGEMENT_ACL}`, ["description Management access", ...VrpRender.aclRules(allow)]);
|
|
@@ -249,7 +255,7 @@ export class VrpRender {
|
|
|
249
255
|
}
|
|
250
256
|
|
|
251
257
|
private snmpAcl(): void {
|
|
252
|
-
const allow = this.device.
|
|
258
|
+
const allow = this.device.monitoring?.snmp?.allow ?? [];
|
|
253
259
|
if (!allow.length) return;
|
|
254
260
|
|
|
255
261
|
this.block(`acl number ${VrpRender.SNMP_ACL}`, ["description SNMP access", ...VrpRender.aclRules(allow)]);
|
|
@@ -269,7 +275,8 @@ export class VrpRender {
|
|
|
269
275
|
private users(): void {
|
|
270
276
|
const lines: string[] = [];
|
|
271
277
|
|
|
272
|
-
for (const [name, user] of Object.entries(this.device.users ?? {})) {
|
|
278
|
+
for (const [name, user] of Object.entries(this.device.system?.users ?? {})) {
|
|
279
|
+
if (!user) continue;
|
|
273
280
|
lines.push(`local-user ${name} password irreversible-cipher ${Secrets.marker(user.password)}`);
|
|
274
281
|
lines.push(`local-user ${name} service-type ssh`);
|
|
275
282
|
lines.push(`local-user ${name} level ${VrpRender.USER_LEVEL[user.role]}`);
|
|
@@ -279,17 +286,19 @@ export class VrpRender {
|
|
|
279
286
|
}
|
|
280
287
|
|
|
281
288
|
private vrfs(): void {
|
|
282
|
-
for (const name of Object.
|
|
289
|
+
for (const [name, vrf] of Object.entries(this.device.routing?.vrfs ?? {})) {
|
|
290
|
+
if (vrf) this.block(`ip vpn-instance ${name}`, ["ipv4-family"]);
|
|
291
|
+
}
|
|
283
292
|
}
|
|
284
293
|
|
|
285
|
-
private static lagMode(lag:
|
|
294
|
+
private static lagMode(lag: Lag): string[] {
|
|
286
295
|
const lines = [lag.mode === "static" ? "mode manual load-balance" : "mode lacp-static"];
|
|
287
296
|
if (lag.description) lines.unshift(`description ${lag.description}`);
|
|
288
297
|
return lines;
|
|
289
298
|
}
|
|
290
299
|
|
|
291
300
|
/** Layer 2 membership of a port or LAG. */
|
|
292
|
-
private switching(settings:
|
|
301
|
+
private switching(settings: Port | Lag): string[] {
|
|
293
302
|
const lines: string[] = [];
|
|
294
303
|
if (settings.access_vlan) lines.push(`port default vlan ${this.vlanId(settings.access_vlan)}`);
|
|
295
304
|
if (settings.trunk_vlans) {
|
|
@@ -340,7 +349,7 @@ export class VrpRender {
|
|
|
340
349
|
return lines;
|
|
341
350
|
}
|
|
342
351
|
|
|
343
|
-
private vlanInterface(name: string, iface:
|
|
352
|
+
private vlanInterface(name: string, iface: VlanInterface): string[] {
|
|
344
353
|
const device = this.device;
|
|
345
354
|
const lines: string[] = [];
|
|
346
355
|
if (iface.description) lines.push(`description ${iface.description}`);
|
|
@@ -356,14 +365,14 @@ export class VrpRender {
|
|
|
356
365
|
|
|
357
366
|
for (const group of iface.vrrp ?? []) lines.push(...VrpRender.vrrpLines(group));
|
|
358
367
|
|
|
359
|
-
const relays = Object.values(device.dhcp_relay ?? {}).filter((relay) => relay.interface === name);
|
|
368
|
+
const relays = Object.values(device.services?.dhcp_relay ?? {}).filter((relay) => relay.interface === name);
|
|
360
369
|
if (relays.length) {
|
|
361
370
|
lines.push("dhcp select relay");
|
|
362
371
|
for (const server of relays.flatMap((relay) => relay.servers)) lines.push(`dhcp relay binding server ip ${server}`);
|
|
363
372
|
}
|
|
364
373
|
|
|
365
374
|
for (const area of Object.values(device.routing?.ospf?.areas ?? {})) {
|
|
366
|
-
const settings = area.interfaces[name
|
|
375
|
+
const settings = area.interfaces[name];
|
|
367
376
|
if (!settings) continue;
|
|
368
377
|
|
|
369
378
|
if (settings.cost !== undefined) lines.push(`ospf cost ${settings.cost}`);
|
|
@@ -375,12 +384,12 @@ export class VrpRender {
|
|
|
375
384
|
}
|
|
376
385
|
|
|
377
386
|
/** A port the config does not declare is shut down. A LAG member holds only its membership. */
|
|
378
|
-
private physicalPort(port: string, settings:
|
|
387
|
+
private physicalPort(port: string, settings: Port | undefined): string[] {
|
|
379
388
|
if (!settings) return ["shutdown"];
|
|
380
389
|
|
|
381
390
|
// A port joins a LAG only while it holds no other setting, so membership comes before its description.
|
|
382
|
-
if (settings.
|
|
383
|
-
const membership = `eth-trunk ${this.lagNumbers.get(settings.
|
|
391
|
+
if (settings.in_lag) {
|
|
392
|
+
const membership = `eth-trunk ${this.lagNumbers.get(settings.in_lag)}`;
|
|
384
393
|
return settings.description ? [membership, `description ${settings.description}`] : [membership];
|
|
385
394
|
}
|
|
386
395
|
|
|
@@ -391,7 +400,7 @@ export class VrpRender {
|
|
|
391
400
|
if (settings.mac_limit !== undefined) lines.push(`mac-address limit maximum ${settings.mac_limit}`);
|
|
392
401
|
if (settings.ethertypes) lines.push(`traffic-filter acl ${this.ethertypeAcls.get(settings.ethertypes.join(" "))} inbound`);
|
|
393
402
|
|
|
394
|
-
const flow = this.device.flow_export;
|
|
403
|
+
const flow = this.device.monitoring?.flow_export;
|
|
395
404
|
const observed = flow?.protocol === "sflow" && (!flow.interfaces || flow.interfaces.includes(port));
|
|
396
405
|
if (observed) {
|
|
397
406
|
for (let index = 0; index < flow!.collectors.length; index++)
|
|
@@ -407,8 +416,9 @@ export class VrpRender {
|
|
|
407
416
|
* Static routes. A route watched by BFD needs a BFD template for its next hop first, from the device's own address on that network, with the device's timers.
|
|
408
417
|
*/
|
|
409
418
|
private staticRoutes(): void {
|
|
410
|
-
const routes = this.device.routing?.
|
|
411
|
-
|
|
419
|
+
const routes = this.device.routing?.static_routes ?? [];
|
|
420
|
+
// A next hop written with its length is written as the address alone.
|
|
421
|
+
const watched = [...new Set(routes.filter((route) => route.bfd && route.next_hop).map((route) => Address.host(route.next_hop!)))];
|
|
412
422
|
|
|
413
423
|
for (const nextHop of watched) {
|
|
414
424
|
const local = VrpRender.localAddressTo(this.device, nextHop);
|
|
@@ -418,7 +428,7 @@ export class VrpRender {
|
|
|
418
428
|
|
|
419
429
|
for (const route of routes) {
|
|
420
430
|
const { addr, len } = Address.split(route.prefix);
|
|
421
|
-
const nextHop = route.blackhole ? "NULL0" : (route.
|
|
431
|
+
const nextHop = route.blackhole ? "NULL0" : route.next_hop ? Address.host(route.next_hop) : this.nameOf(route.interface!);
|
|
422
432
|
const vrf = route.vrf ? ` vpn-instance ${route.vrf}` : "";
|
|
423
433
|
const preference = route.distance ? ` preference ${route.distance}` : "";
|
|
424
434
|
const description = route.description ? ` description ${route.description}` : "";
|
|
@@ -433,8 +443,8 @@ export class VrpRender {
|
|
|
433
443
|
|
|
434
444
|
/** The device's own IPv4 address on the network that holds `nextHop`. `unsupported()` refuses a watched route without one. */
|
|
435
445
|
static localAddressTo(device: Device, nextHop: string): string | undefined {
|
|
436
|
-
for (const
|
|
437
|
-
for (const entry of
|
|
446
|
+
for (const { settings } of Derive.interfaces(device)) {
|
|
447
|
+
for (const entry of settings.addresses ?? []) {
|
|
438
448
|
const address = typeof entry === "string" ? entry : entry.address;
|
|
439
449
|
if (Address.contains(Address.prefixOf(address), nextHop)) return Address.host(address);
|
|
440
450
|
}
|
|
@@ -482,7 +492,7 @@ export class VrpRender {
|
|
|
482
492
|
private wantsBfd(): boolean {
|
|
483
493
|
const areas = Object.values(this.device.routing?.ospf?.areas ?? {});
|
|
484
494
|
const onOspf = areas.some((area) => Object.values(area.interfaces).some((settings) => settings?.bfd));
|
|
485
|
-
const onStatic = (this.device.routing?.
|
|
495
|
+
const onStatic = (this.device.routing?.static_routes ?? []).some((route) => route.bfd);
|
|
486
496
|
|
|
487
497
|
return onOspf || onStatic;
|
|
488
498
|
}
|
|
@@ -490,7 +500,7 @@ export class VrpRender {
|
|
|
490
500
|
/** sFlow from the management address, to each collector in order. */
|
|
491
501
|
private flowExport(): void {
|
|
492
502
|
const device = this.device;
|
|
493
|
-
const flow = device.flow_export;
|
|
503
|
+
const flow = device.monitoring?.flow_export;
|
|
494
504
|
if (flow?.protocol !== "sflow") return;
|
|
495
505
|
|
|
496
506
|
if (device.connection.host) this.line(`sflow agent ip ${device.connection.host}`);
|
|
@@ -505,10 +515,10 @@ export class VrpRender {
|
|
|
505
515
|
|
|
506
516
|
/** The log files' level and count, then each syslog collector. A default the device does not print is not written: level `debugging`, 200 files, port 514. */
|
|
507
517
|
private logging(): void {
|
|
508
|
-
const
|
|
509
|
-
|
|
518
|
+
const local = this.device.system?.logging?.local;
|
|
519
|
+
const collectors = this.device.monitoring?.syslog;
|
|
520
|
+
if (!this.device.system?.logging && !collectors) return;
|
|
510
521
|
|
|
511
|
-
const local = logging.local;
|
|
512
522
|
if (local && local.level !== "debug") {
|
|
513
523
|
this.line(`info-center source default channel ${VrpRender.LOGFILE_CHANNEL} log level ${VrpRender.LOG_LEVEL[local.level]}`);
|
|
514
524
|
}
|
|
@@ -516,7 +526,7 @@ export class VrpRender {
|
|
|
516
526
|
if (local?.files !== undefined && local.files !== VrpRender.LOGFILES_DEFAULT)
|
|
517
527
|
this.line(`info-center max-logfile-number ${local.files}`);
|
|
518
528
|
|
|
519
|
-
for (const collector of
|
|
529
|
+
for (const collector of collectors ?? []) {
|
|
520
530
|
const level = collector.level === "debug" ? "" : ` level ${VrpRender.LOG_LEVEL[collector.level]}`;
|
|
521
531
|
const port = collector.port !== undefined && collector.port !== VrpRender.SYSLOG_PORT ? ` port ${collector.port}` : "";
|
|
522
532
|
this.line(`info-center loghost ${collector.address}${level}${port}`);
|
|
@@ -539,12 +549,12 @@ export class VrpRender {
|
|
|
539
549
|
/** SNMP version 2c with one read community, version 3 with users in one read-only group, or both. */
|
|
540
550
|
private snmp(): void {
|
|
541
551
|
const device = this.device;
|
|
542
|
-
const snmp = device.
|
|
552
|
+
const snmp = device.monitoring?.snmp;
|
|
543
553
|
if (!snmp) return;
|
|
544
554
|
|
|
545
555
|
const versions = [snmp.community ? "v2c" : "", snmp.users ? "v3" : ""].filter(Boolean).join(" ");
|
|
546
556
|
const own = (snmp.allow?.length ?? 0) > 0;
|
|
547
|
-
const guarded = (device.
|
|
557
|
+
const guarded = VrpRender.managementAllow(device).length > 0;
|
|
548
558
|
const acl = own ? ` acl ${VrpRender.SNMP_ACL}` : guarded ? ` acl ${VrpRender.MANAGEMENT_ACL}` : "";
|
|
549
559
|
|
|
550
560
|
this.line("snmp-agent");
|
|
@@ -577,11 +587,12 @@ export class VrpRender {
|
|
|
577
587
|
const ssh = device.management?.ssh;
|
|
578
588
|
if (!ssh) return;
|
|
579
589
|
|
|
580
|
-
const guarded = (device.
|
|
590
|
+
const guarded = VrpRender.managementAllow(device).length > 0;
|
|
581
591
|
|
|
582
592
|
this.line("stelnet server enable");
|
|
583
593
|
|
|
584
|
-
for (const user of Object.
|
|
594
|
+
for (const [user, settings] of Object.entries(device.system?.users ?? {})) {
|
|
595
|
+
if (!settings) continue;
|
|
585
596
|
this.line(`ssh user ${user}`);
|
|
586
597
|
this.line(`ssh user ${user} authentication-type password`);
|
|
587
598
|
this.line(`ssh user ${user} service-type stelnet`);
|