@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
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { Address } from "../../core/address";
|
|
2
|
+
import { Secrets } from "../../core/secrets";
|
|
3
|
+
import { Utils } from "../../core/utils";
|
|
4
|
+
import type { Device, Ethertype, Port, Prefix, Severity, SnmpUser, Switched, Vrrp } from "../../schema";
|
|
5
|
+
import { Catalog } from "../catalog";
|
|
6
|
+
import { Derive } from "../derive";
|
|
7
|
+
|
|
8
|
+
type AnyPort = Port<string, string, string, string>;
|
|
9
|
+
|
|
10
|
+
/** The configuration a Raisecom ROS device should run. */
|
|
11
|
+
export class RaisecomRosRender {
|
|
12
|
+
/** The access-list that guards every management service. */
|
|
13
|
+
static readonly MANAGEMENT_ACL = 1000;
|
|
14
|
+
|
|
15
|
+
/** The access-list that guards SNMP when it has sources of its own. */
|
|
16
|
+
static readonly SNMP_ACL = 1001;
|
|
17
|
+
|
|
18
|
+
/** A log level as the platform prints it, a number. It takes the names too (`warnings`), but prints the number. */
|
|
19
|
+
private static readonly LOG_LEVELS: Record<Severity, number> = {
|
|
20
|
+
emergency: 0,
|
|
21
|
+
alert: 1,
|
|
22
|
+
critical: 2,
|
|
23
|
+
error: 3,
|
|
24
|
+
warning: 4,
|
|
25
|
+
notice: 5,
|
|
26
|
+
info: 6,
|
|
27
|
+
debug: 7,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** The most MAC addresses port security takes on a port, and its default. */
|
|
31
|
+
static readonly MAC_LIMIT_MAX = 1024;
|
|
32
|
+
|
|
33
|
+
/** A kind of frame as a MAC access-list rule matches it, in the form the device prints. It takes a type as hex without `0x`, and names IPv4 and ARP. */
|
|
34
|
+
private static readonly ETHERTYPES: Record<Ethertype, string> = { ipv4: "ip", arp: "arp", ipv6: "86dd ffff" };
|
|
35
|
+
|
|
36
|
+
/** The log level and UDP port a log host has unless told otherwise. Neither prints. */
|
|
37
|
+
private static readonly LOG_DEFAULTS = { level: 6, port: 514 };
|
|
38
|
+
|
|
39
|
+
/** The SNMPv3 authentication each `auth` takes on this platform. One missing here cannot be expressed. */
|
|
40
|
+
static readonly SNMP_AUTH: Partial<Record<SnmpUser["auth"], string>> = { sha1: "sha" };
|
|
41
|
+
|
|
42
|
+
/** The OSPF process number. */
|
|
43
|
+
private static readonly OSPF_PROCESS = 1;
|
|
44
|
+
|
|
45
|
+
/** The SNMPv3 access group every user belongs to, read-only on the whole tree. */
|
|
46
|
+
private static readonly SNMP_GROUP = "circuit";
|
|
47
|
+
|
|
48
|
+
/** `99,200-201,999`, the platform's compression of a VLAN list. */
|
|
49
|
+
static vlanList(ids: number[]): string {
|
|
50
|
+
const sorted = [...new Set(ids)].sort((a, b) => a - b);
|
|
51
|
+
const ranges: string[] = [];
|
|
52
|
+
|
|
53
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
54
|
+
let end = i;
|
|
55
|
+
while (sorted[end + 1] === sorted[end]! + 1) end++;
|
|
56
|
+
ranges.push(i === end ? `${sorted[i]}` : `${sorted[i]}-${sorted[end]}`);
|
|
57
|
+
i = end;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return ranges.join(",");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The whole configuration of a device. */
|
|
64
|
+
static of(device: Device): string {
|
|
65
|
+
const lines: string[] = [];
|
|
66
|
+
const vlans = device.vlans ?? {};
|
|
67
|
+
const vlanId = (name: string) => vlans[name]!.id;
|
|
68
|
+
const management = device.management ?? {};
|
|
69
|
+
const allow = management.allow ?? [];
|
|
70
|
+
|
|
71
|
+
const ids = Object.values(vlans).map((vlan) => vlan.id);
|
|
72
|
+
if (ids.length) lines.push(`create vlan ${RaisecomRosRender.vlanList(ids)} active`, "!");
|
|
73
|
+
|
|
74
|
+
const lagNumbers = Derive.lagIds(device);
|
|
75
|
+
|
|
76
|
+
for (const [name, lag] of Object.entries(device.interfaces ?? {})) {
|
|
77
|
+
if (lag.type !== "lag") continue;
|
|
78
|
+
|
|
79
|
+
lines.push(`interface port-channel ${lagNumbers.get(name)}`);
|
|
80
|
+
if (lag.description) lines.push(`description ${lag.description}`);
|
|
81
|
+
if (lag.mode === "static") lines.push("mode manual");
|
|
82
|
+
lines.push(...RaisecomRosRender.switching(lag, vlanId), "!");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (allow.length) lines.push(...RaisecomRosRender.accessList(RaisecomRosRender.MANAGEMENT_ACL, "management", allow));
|
|
86
|
+
const snmpSources = management.snmp?.allow;
|
|
87
|
+
if (snmpSources) lines.push(...RaisecomRosRender.accessList(RaisecomRosRender.SNMP_ACL, "snmp", snmpSources));
|
|
88
|
+
|
|
89
|
+
const ports = Catalog.ports(device.platform, device.model);
|
|
90
|
+
|
|
91
|
+
// Each port that takes only some kinds of frame has a MAC access-list of its own, so changing one releases only that port.
|
|
92
|
+
ports.forEach((port, index) => {
|
|
93
|
+
const ethertypes = device.ports?.[port as keyof typeof device.ports]?.ethertypes;
|
|
94
|
+
if (!ethertypes) return;
|
|
95
|
+
|
|
96
|
+
lines.push(`access-list ${RaisecomRosRender.ethertypeAcl(index)}`);
|
|
97
|
+
ethertypes.forEach((type, rule) =>
|
|
98
|
+
lines.push(` rule ${(rule + 1) * 10} permit any any ethertype ${RaisecomRosRender.ETHERTYPES[type]}`)
|
|
99
|
+
);
|
|
100
|
+
lines.push(" rule 100 deny any any", "!");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
for (const [name, user] of Object.entries(device.users ?? {}))
|
|
104
|
+
lines.push(`user name ${name} password ${Secrets.marker(user.password)}`);
|
|
105
|
+
if (management.privilege_password) lines.push(`enable password ${Secrets.marker(management.privilege_password)}`);
|
|
106
|
+
lines.push(`hostname ${device.name}`);
|
|
107
|
+
|
|
108
|
+
const timezone = device.system?.timezone;
|
|
109
|
+
if (timezone) {
|
|
110
|
+
const minutes = Utils.timeZoneOffset(timezone);
|
|
111
|
+
const absolute = Math.abs(minutes);
|
|
112
|
+
lines.push(`clock timezone ${minutes < 0 ? "-" : "+"} ${Math.floor(absolute / 60)} ${absolute % 60} 0`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
lines.push("!");
|
|
116
|
+
|
|
117
|
+
ports.forEach((port, index) => {
|
|
118
|
+
const settings = device.ports?.[port as keyof typeof device.ports];
|
|
119
|
+
lines.push(
|
|
120
|
+
`interface ${Catalog.spell(device.platform, device.model, port)}`,
|
|
121
|
+
...RaisecomRosRender.physicalPort(settings, vlanId, lagNumbers),
|
|
122
|
+
...(settings?.ethertypes ? [`filter ingress access-list ${RaisecomRosRender.ethertypeAcl(index)}`] : []),
|
|
123
|
+
"!"
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const ospf = device.routing?.ospf;
|
|
128
|
+
|
|
129
|
+
for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
|
|
130
|
+
if (iface.type !== "vlan") continue;
|
|
131
|
+
|
|
132
|
+
lines.push(`interface vlan ${vlanId(iface.vlan)}`);
|
|
133
|
+
|
|
134
|
+
for (const entry of iface.addresses ?? []) {
|
|
135
|
+
const address = typeof entry === "string" ? entry : entry.address;
|
|
136
|
+
if (Address.family(address) === "ipv4")
|
|
137
|
+
lines.push(`ip address ${Address.host(address)} ${Address.netmask(Address.split(address).len)}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const ospfSettings = Object.values(ospf?.areas ?? {}).find((area) => area.interfaces[name])?.interfaces[name];
|
|
141
|
+
if (ospfSettings?.network === "point-to-point") lines.push("ip ospf network ptp");
|
|
142
|
+
if (ospfSettings?.cost !== undefined) lines.push(`ip ospf cost ${ospfSettings.cost}`);
|
|
143
|
+
if (ospfSettings?.passive) lines.push("ip ospf passive-interface enable");
|
|
144
|
+
if (ospfSettings?.bfd) lines.push("ip ospf bfd");
|
|
145
|
+
|
|
146
|
+
for (const group of iface.vrrp ?? []) lines.push(...RaisecomRosRender.vrrp(group));
|
|
147
|
+
|
|
148
|
+
lines.push("!");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (ospf) {
|
|
152
|
+
const routerId = device.routing?.router_id;
|
|
153
|
+
lines.push(`router ospf ${RaisecomRosRender.OSPF_PROCESS}${routerId ? ` router-id ${routerId}` : ""}`);
|
|
154
|
+
|
|
155
|
+
for (const [areaId, area] of Object.entries(ospf.areas)) {
|
|
156
|
+
for (const iface of Object.keys(area.interfaces)) {
|
|
157
|
+
for (const prefix of Derive.ipv4Networks(device, iface)) {
|
|
158
|
+
lines.push(`network ${Address.host(prefix)} ${Address.wildcard(Address.split(prefix).len)} area ${areaId}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
for (const source of ospf.redistribute ?? []) lines.push(`redistribute ${source}`);
|
|
164
|
+
lines.push("!");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Storm control acts only once detection is on.
|
|
168
|
+
if (Object.values(device.ports ?? {}).some((port) => port?.storm_control)) lines.push("storm-control detection enable");
|
|
169
|
+
if (device.system?.transceiver_monitoring) lines.push("transceiver ddm enable");
|
|
170
|
+
// LLDP and spanning tree are off by default and turned on by the config.
|
|
171
|
+
if (device.lldp) lines.push("lldp enable");
|
|
172
|
+
if (device.stp) {
|
|
173
|
+
lines.push("spanning-tree enable");
|
|
174
|
+
if (device.stp.priority !== undefined) lines.push(`spanning-tree priority ${device.stp.priority}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// The factory communities survive a reset, and one of them can write, so they are always removed.
|
|
178
|
+
lines.push("no snmp-server community public", "no snmp-server community private");
|
|
179
|
+
const snmp = management.snmp;
|
|
180
|
+
if (snmp?.community) lines.push(`snmp-server community ${Secrets.marker(snmp.community)} ro`);
|
|
181
|
+
|
|
182
|
+
const snmpUsers = Object.entries(snmp?.users ?? {});
|
|
183
|
+
if (snmpUsers.length) lines.push(`snmp-server access ${RaisecomRosRender.SNMP_GROUP} read internet usm authpriv`);
|
|
184
|
+
|
|
185
|
+
for (const [name, user] of snmpUsers) {
|
|
186
|
+
const authentication = `authentication ${RaisecomRosRender.SNMP_AUTH[user.auth]} ${Secrets.marker(user.auth_password)}`;
|
|
187
|
+
const privacy = `privacy ${user.privacy} ${Secrets.marker(user.privacy_password)}`;
|
|
188
|
+
lines.push(
|
|
189
|
+
`snmp-server user ${name} ${authentication} ${privacy}`,
|
|
190
|
+
`snmp-server group ${RaisecomRosRender.SNMP_GROUP} user ${name} usm`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const snmpAcl = snmpSources ? RaisecomRosRender.SNMP_ACL : allow.length ? RaisecomRosRender.MANAGEMENT_ACL : undefined;
|
|
195
|
+
if ((snmp?.community || snmpUsers.length) && snmpAcl) lines.push(`snmp-server access-list ${snmpAcl}`);
|
|
196
|
+
|
|
197
|
+
// A static route watched by BFD tracks a BFD session, one per next hop, bound to the VLAN interface the next hop is on.
|
|
198
|
+
const sessions = RaisecomRosRender.bfdSessions(device);
|
|
199
|
+
|
|
200
|
+
for (const [via, session] of sessions) {
|
|
201
|
+
lines.push(`bfd ${session.id} bind peer-ip ${via} interface vlan ${vlanId(session.vlan)}`, "session enable", "!");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (const route of device.routing?.static ?? []) {
|
|
205
|
+
const { addr, len } = Address.split(route.prefix);
|
|
206
|
+
if (Address.family(addr) !== "ipv4" || !route.via) continue;
|
|
207
|
+
|
|
208
|
+
const session = route.bfd ? sessions.get(route.via) : undefined;
|
|
209
|
+
lines.push(`ip route ${addr} ${Address.netmask(len)} ${route.via}${session ? ` track bfd ${session.id}` : ""}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (management.telnet) {
|
|
213
|
+
lines.push("telnet-server enable");
|
|
214
|
+
if (allow.length) lines.push(`telnet-server access-list ${RaisecomRosRender.MANAGEMENT_ACL}`);
|
|
215
|
+
} else lines.push("telnet-server disable");
|
|
216
|
+
|
|
217
|
+
const ssh = management.ssh;
|
|
218
|
+
if (ssh?.auth_timeout) lines.push(`ssh2 server authentication-timeout ${ssh.auth_timeout}`);
|
|
219
|
+
if (ssh?.auth_retries) lines.push(`ssh2 server authentication-retries ${ssh.auth_retries}`);
|
|
220
|
+
if (ssh && allow.length) lines.push(`ssh2 access-list ${RaisecomRosRender.MANAGEMENT_ACL}`);
|
|
221
|
+
|
|
222
|
+
const ntp = device.system?.ntp;
|
|
223
|
+
if (ntp?.servers.length) lines.push("ntp enable", ...ntp.servers.map((server) => `ntp server ${server}`));
|
|
224
|
+
|
|
225
|
+
for (const host of device.system?.logging?.remote ?? []) {
|
|
226
|
+
const level = RaisecomRosRender.LOG_LEVELS[host.level];
|
|
227
|
+
const port = host.port ?? RaisecomRosRender.LOG_DEFAULTS.port;
|
|
228
|
+
let line = `logging host ${host.address}`;
|
|
229
|
+
if (level !== RaisecomRosRender.LOG_DEFAULTS.level) line += ` ${level}`;
|
|
230
|
+
if (port !== RaisecomRosRender.LOG_DEFAULTS.port) line += ` udpport ${port}`;
|
|
231
|
+
lines.push(line);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return lines.join("\n") + "\n";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** An access-list permitting `sources`, then denying the rest. Its description takes one word. */
|
|
238
|
+
private static accessList(number: number, description: string, sources: readonly Prefix[]): string[] {
|
|
239
|
+
const rules = sources.map(
|
|
240
|
+
(cidr, index) => ` rule ${(index + 1) * 10} permit ${Address.host(cidr)} ${Address.netmask(Address.split(cidr).len)}`
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Stated, so the list reads the same without knowing the firmware's implicit end.
|
|
244
|
+
return [`access-list ${number}`, ` description ${description}`, ...rules, " rule 100 deny any", "!"];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Access or trunk membership, for a port or a LAG. */
|
|
248
|
+
private static switching(settings: Switched<string>, vlanId: (name: string) => number): string[] {
|
|
249
|
+
const lines: string[] = [];
|
|
250
|
+
if (settings.access_vlan) lines.push(`switchport access vlan ${vlanId(settings.access_vlan)}`);
|
|
251
|
+
|
|
252
|
+
if (settings.trunk_vlans) {
|
|
253
|
+
if (settings.native_vlan) lines.push(`switchport trunk native vlan ${vlanId(settings.native_vlan)}`);
|
|
254
|
+
lines.push(
|
|
255
|
+
`switchport trunk allowed vlan ${RaisecomRosRender.vlanList(settings.trunk_vlans.map(vlanId))}`,
|
|
256
|
+
"switchport mode trunk"
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return lines;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** A VRRP group. The platform enables a group once it has an address, and preempts unless told not to. */
|
|
264
|
+
private static vrrp(group: Vrrp): string[] {
|
|
265
|
+
const lines = [`vrrp ${group.id} ip ${group.address}`];
|
|
266
|
+
if (group.interval !== undefined && group.interval !== 1)
|
|
267
|
+
lines.push(`vrrp ${group.id} timers advertise-interval ${group.interval}`);
|
|
268
|
+
if (group.priority !== undefined && group.priority !== 100) lines.push(`vrrp ${group.id} priority ${group.priority}`);
|
|
269
|
+
if (!group.preempt) lines.push(`no vrrp ${group.id} preempt`);
|
|
270
|
+
return lines;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** A port the config does not declare is shut down. A LAG member holds its membership and description; the LAG holds the rest. */
|
|
274
|
+
private static physicalPort(
|
|
275
|
+
settings: AnyPort | undefined,
|
|
276
|
+
vlanId: (name: string) => number,
|
|
277
|
+
lagNumbers: Map<string, number>
|
|
278
|
+
): string[] {
|
|
279
|
+
if (!settings) return ["shutdown"];
|
|
280
|
+
|
|
281
|
+
const lines: string[] = [];
|
|
282
|
+
if (settings.description) lines.push(`description ${settings.description}`);
|
|
283
|
+
if (settings.lag) return [...lines, `port-channel ${lagNumbers.get(settings.lag)}`];
|
|
284
|
+
|
|
285
|
+
lines.push(...RaisecomRosRender.switching(settings, vlanId));
|
|
286
|
+
|
|
287
|
+
if (settings.lldp === false) lines.push("lldp disable");
|
|
288
|
+
if (settings.stp === false) lines.push("spanning-tree disable");
|
|
289
|
+
const broadcast = settings.storm_control?.broadcast;
|
|
290
|
+
if (broadcast && "pps" in broadcast) lines.push("storm-control mode pps", `storm-control broadcast pps ${broadcast.pps}`);
|
|
291
|
+
|
|
292
|
+
// Port security drops frames from any source past the limit (its `protect` action, the default). The most it takes, 1024, is its default and prints nothing.
|
|
293
|
+
if (settings.mac_limit !== undefined) {
|
|
294
|
+
lines.push("switchport port-security");
|
|
295
|
+
if (settings.mac_limit !== RaisecomRosRender.MAC_LIMIT_MAX)
|
|
296
|
+
lines.push(`switchport port-security maximum ${settings.mac_limit}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return lines;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** The MAC access-list of the port at `index` in the catalog, for its `ethertypes`. MAC access-lists are 3000 and up. */
|
|
303
|
+
/** The BFD session for each next hop a static route watches, numbered from 1 in the order the routes come, with the VLAN its interface is on. A next hop on no VLAN interface's network has none. */
|
|
304
|
+
static bfdSessions(device: Device): Map<string, { id: number; vlan: string }> {
|
|
305
|
+
const sessions = new Map<string, { id: number; vlan: string }>();
|
|
306
|
+
|
|
307
|
+
for (const route of device.routing?.static ?? []) {
|
|
308
|
+
if (!route.bfd || !route.via || sessions.has(route.via)) continue;
|
|
309
|
+
|
|
310
|
+
const holder = Object.values(device.interfaces ?? {}).find(
|
|
311
|
+
(iface) =>
|
|
312
|
+
iface.type === "vlan" &&
|
|
313
|
+
(iface.addresses ?? []).some((entry) => {
|
|
314
|
+
const address = typeof entry === "string" ? entry : entry.address;
|
|
315
|
+
return Address.family(address) === "ipv4" && Address.contains(Address.prefixOf(address), route.via!);
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (holder?.type === "vlan") sessions.set(route.via, { id: sessions.size + 1, vlan: holder.vlan });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return sessions;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
static ethertypeAcl(index: number): number {
|
|
326
|
+
return 3001 + index;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
@@ -10,7 +10,9 @@ Behaviour that looks like something else. Each one has been hit on a real device
|
|
|
10
10
|
- `/routing bgp connection add` without `local.role` is refused, so the render always writes one. On 7.23 one without `instance` is refused too, `missing value(s) of argument(s) instance`, although the export never prints `instance=default`.
|
|
11
11
|
- A connection without `input.filter` accepts everything. A neighbor without a policy is given `REJECT-ALL`.
|
|
12
12
|
- A session edited repeatedly can stick: SYN both ways, no RST, no log. Remove the connection and add it again.
|
|
13
|
-
- A session that never established is missing from `/routing bgp session print`; look at `/routing bgp connection print`.
|
|
13
|
+
- A session that never established is missing from `/routing bgp session print`; look at `/routing bgp connection print`. One that is trying prints `established` empty, not `false`, and no uptime (7.23.5).
|
|
14
|
+
- A BGP session is named `<connection>-<n>`, and a renamed connection keeps its session's old name until the session restarts. `circuit bgp` matches a session by the neighbor's address first.
|
|
15
|
+
- `time>` in a `/log print` filter does not filter. `circuit bgp --log` keeps the lines of the last minutes itself, against the device's clock.
|
|
14
16
|
- `tcp-md5-key` set from the CLI has been seen discarded without an error, and a mismatch logs nothing. Confirm with `/tool sniffer quick port=179`.
|
|
15
17
|
- Incoming GRE is subject to the input chain. Accept it before testing the tunnel.
|
|
16
18
|
- A WireGuard peer's name is unique on the device, and a new key under the same name is refused as a new peer, `entry with this name already exists`. The plan finds a peer by name, so a new key is a `set`.
|
|
@@ -44,6 +46,18 @@ Behaviour that looks like something else. Each one has been hit on a real device
|
|
|
44
46
|
- On the serial console, the prompt can come twice after a login. The console echoes a long command wrapped at 80 columns, and a `print` that fills the screen stops at `-- [Q quit|D dump|down]`, where `D` prints the rest.
|
|
45
47
|
- Every VLAN interface on the bridge has the bridge's MAC, and the bridge keeps a frame to that MAC on whatever VLAN it arrives. A gateway in front of a transparent firewall then never gets its replies: they come back on the inside VLAN and stop at the bridge. A MACVLAN on the VLAN interface gives the gateway a MAC of its own, and its replies cross the firewall back; proved with a FortiGate 3700D in transparent mode, the session offloaded to its NP6.
|
|
46
48
|
- A second VLAN interface with the same VLAN ID on the same interface is refused, `already have such vlan`, so a VLAN interface is known by its interface and VLAN ID, and a new name is a rename.
|
|
47
|
-
- A switch rule the chip cannot hold is flagged `I`, invalid, and every rule after it is invalid too, with no log line. On a CCR2216 (98DX8525, 7.23) it came at the sixth distinct `dst-port` range, with 66 rules in all, far below the 1,024 MikroTik lists: the amplifier drops toward every VLAN after it did nothing. Few distinct ranges, and
|
|
49
|
+
- A switch rule the chip cannot hold is flagged `I`, invalid, and every rule after it is invalid too, with no log line. On a CCR2216 (98DX8525, 7.23) it came at the sixth distinct `dst-port` range, with 66 rules in all, far below the 1,024 MikroTik lists: the amplifier drops toward every VLAN after it did nothing. Few distinct ranges. `apply` counts `print count-only where invalid` after a plan that changed switch rules, and fails when it is not 0. The ranges ran out, not the count: on the same router on 7.23.5, 66 and then 70 rules with two distinct ranges were all valid, `print count-only where invalid` printing `0`. `validate` warns above 5 distinct ranges on a CCR2216.
|
|
48
50
|
- A MACVLAN on a VLAN interface is not routed in the switch chip: its connected route never takes the `H` flag, even with `l3-hw-offloading=yes` on the VLAN under it.
|
|
49
51
|
- A `/system logging` rule naming an action that does not exist yet is refused, `input does not match any value of action`, and `:parse` does not catch it. The action is added first; seen on a CCR2216 on 7.23.5, where the refusal restored the backup.
|
|
52
|
+
- A name is unique in a menu, also where an entry is known by something else: a VLAN interface by its interface and VLAN ID, a BGP connection by its peer address. A new entry under a name the old one still has is refused, and on 7.23.5 the refusal restored the backup and restarted the router. Circuit changes a VLAN interface's VLAN ID with a `set`, so what points at it by name stays attached, and removes any other such entry before adding it again. Proved on a CCR2216 on 7.23.5: the `set vlan-id` took, and the address on the interface stayed.
|
|
53
|
+
- A CCR2216 with 12 active members in one LAG crash-looped, a kernel failure each time LACP formed, for 40 minutes. RouterOS took the 12 in the config and failed only once more than 8 came up. `validate` refuses a LAG of more than 8 on a CCR2216.
|
|
54
|
+
- `/ip neighbor` reports the far port in `interface-name`, which held the far port's description, not its name, from both a Raisecom RAX721 and a FortiGate 3700D (CCR2216, 7.23.5). No other field holds the port. The reader maps a description to the far device's port when exactly one of its ports in the network has it.
|
|
55
|
+
- A neighbor's `interface` is a list: the port, then the LAG and the bridge it is in, `sfp28-1;infra-lag01-fwl;bridge`. Joined to a string in a script, a list repeats the whole line for each of its items; `:tostr` first.
|
|
56
|
+
- LLDP is only received on the interfaces in `discover-interface-list`. On a VLAN interface alone, as the management one, a neighbor that sends untagged LLDP is never seen.
|
|
57
|
+
- A static route's `check-gateway` takes `arp`, `none` or `ping` on 7.23.5, not `bfd`: a bare `bfd` is a syntax error. A quoted value passes `:parse` whatever it is, then the run refuses it, `input does not match any value of check-gateway`; that refusal restored the backup and restarted the router. BFD on a static route stays refused.
|
|
58
|
+
- `/routing bfd configuration interfaces=all` does not cover a multihop session: its BFD session shows `BFD forbidden for destination address`. A configuration with the session's `addresses` makes it active. The render adds one for each multihop session with BFD.
|
|
59
|
+
- A BGP connection with `use-bfd=yes` stays Idle until its BFD session is up, so a peer that does not run BFD never comes up: a multihop session that had been up for hours stayed down five minutes until `use-bfd` went off (CCR2216, 7.23.5). A BGP session asks for BFD only once it has a session; a connection to an address that never answers shows no BFD session at all.
|
|
60
|
+
- BFD timers never set print nothing, and a session then runs at `min-rx=200ms min-tx=200ms multiplier=5`, which print once set. The plan sets them back to those. A multihop BFD session that is down sends once a second whatever its timers.
|
|
61
|
+
- `use-bfd=no`, set on a connection that had `use-bfd=yes`, is printed by the export; it is the default, and the plan sets it to take BFD off.
|
|
62
|
+
- No MAC learning limit on 7.23.5: `/interface bridge port` has `learn` but no limit, and `/interface ethernet switch port` has none (CCR2216). `mac_limit` stays refused.
|
|
63
|
+
- A switch rule matches a kind of frame with `mac-protocol` (`ip`, `ipv6`, `arp` among 23 kinds), but cannot match "not" one. An ethertype filter is a rule that lets each kind through and a last one with an empty `new-dst-ports` for the rest, after the port's ACL rules, which match only `ip` or `ipv6`. Four such rules on a free port took the router to 70 switch rules, none invalid (CCR2216, 7.23.5).
|