@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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Values every network writes the same way, each from a public standard it names. Plain data, in one `presets` object: pick from it, or
|
|
2
|
+
// place its rules where they go in an ordered list with `merge`. Data is snake_case, like every field.
|
|
3
|
+
|
|
4
|
+
import type { Prefix } from "./common";
|
|
5
|
+
import type { FilterRule, Ports } from "./firewall";
|
|
6
|
+
import type { RouteRule } from "./routing";
|
|
7
|
+
|
|
8
|
+
const bogons = {
|
|
9
|
+
ipv4: {
|
|
10
|
+
/** "This network", RFC 791 and RFC 1122. */
|
|
11
|
+
this_network: "0.0.0.0/8",
|
|
12
|
+
/** Private use, RFC 1918. */
|
|
13
|
+
private10: "10.0.0.0/8",
|
|
14
|
+
/** Shared address space for carrier-grade NAT, RFC 6598. */
|
|
15
|
+
shared_address_space: "100.64.0.0/10",
|
|
16
|
+
/** Loopback, RFC 1122. */
|
|
17
|
+
loopback: "127.0.0.0/8",
|
|
18
|
+
/** Link-local, RFC 3927. */
|
|
19
|
+
link_local: "169.254.0.0/16",
|
|
20
|
+
/** Private use, RFC 1918. */
|
|
21
|
+
private172: "172.16.0.0/12",
|
|
22
|
+
/** IETF protocol assignments, RFC 6890. */
|
|
23
|
+
ietf_protocols: "192.0.0.0/24",
|
|
24
|
+
/** Documentation, TEST-NET-1, RFC 5737. */
|
|
25
|
+
documentation1: "192.0.2.0/24",
|
|
26
|
+
/** Private use, RFC 1918. */
|
|
27
|
+
private192: "192.168.0.0/16",
|
|
28
|
+
/** Benchmarking, RFC 2544. */
|
|
29
|
+
benchmarking: "198.18.0.0/15",
|
|
30
|
+
/** Documentation, TEST-NET-2, RFC 5737. */
|
|
31
|
+
documentation2: "198.51.100.0/24",
|
|
32
|
+
/** Documentation, TEST-NET-3, RFC 5737. */
|
|
33
|
+
documentation3: "203.0.113.0/24",
|
|
34
|
+
/** Multicast, RFC 5771. */
|
|
35
|
+
multicast: "224.0.0.0/4",
|
|
36
|
+
/** Reserved for future use, RFC 1112. */
|
|
37
|
+
reserved: "240.0.0.0/4",
|
|
38
|
+
},
|
|
39
|
+
ipv6: {
|
|
40
|
+
/** The unspecified address, RFC 4291. */
|
|
41
|
+
unspecified: "::/128",
|
|
42
|
+
/** Loopback, RFC 4291. */
|
|
43
|
+
loopback: "::1/128",
|
|
44
|
+
/** IPv4-mapped addresses, RFC 4291. */
|
|
45
|
+
ipv4_mapped: "::ffff:0:0/96",
|
|
46
|
+
/** Discard-only, RFC 6666. */
|
|
47
|
+
discard_only: "100::/64",
|
|
48
|
+
/** Documentation, RFC 3849. */
|
|
49
|
+
documentation: "2001:db8::/32",
|
|
50
|
+
/** Unique local, RFC 4193. */
|
|
51
|
+
unique_local: "fc00::/7",
|
|
52
|
+
/** Link-local, RFC 4291. */
|
|
53
|
+
link_local: "fe80::/10",
|
|
54
|
+
/** Multicast, RFC 4291. */
|
|
55
|
+
multicast: "ff00::/8",
|
|
56
|
+
},
|
|
57
|
+
} as const satisfies Record<"ipv4" | "ipv6", Record<string, Prefix>>;
|
|
58
|
+
|
|
59
|
+
const bgp_sanity: readonly RouteRule<never>[] = [
|
|
60
|
+
{ description: "IPv4 shorter than /8", if: { family: "ipv4", prefix_length: { max: 7 } }, then: "reject" },
|
|
61
|
+
{ description: "IPv4 longer than /24", if: { family: "ipv4", prefix_length: { min: 25 } }, then: "reject" },
|
|
62
|
+
{ description: "IPv6 shorter than /16", if: { family: "ipv6", prefix_length: { max: 15 } }, then: "reject" },
|
|
63
|
+
{ description: "IPv6 longer than /48", if: { family: "ipv6", prefix_length: { min: 49 } }, then: "reject" },
|
|
64
|
+
{ description: "RPKI invalid", if: { rpki: "invalid" }, then: "reject" },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const bad_tcp_flags: readonly FilterRule<never, never, never>[] = [
|
|
68
|
+
{ description: "Bad flags FIN+SYN", if: { protocol: "tcp", tcp_flags: { set: ["fin", "syn"], unset: [] } }, then: "drop" },
|
|
69
|
+
{ description: "Bad flags SYN+RST", if: { protocol: "tcp", tcp_flags: { set: ["syn", "rst"], unset: [] } }, then: "drop" },
|
|
70
|
+
{ description: "Bad flags FIN+RST", if: { protocol: "tcp", tcp_flags: { set: ["fin", "rst"], unset: [] } }, then: "drop" },
|
|
71
|
+
{ description: "Bad flags FIN without ACK", if: { protocol: "tcp", tcp_flags: { set: ["fin"], unset: ["ack"] } }, then: "drop" },
|
|
72
|
+
{ description: "Bad flags FIN+URG", if: { protocol: "tcp", tcp_flags: { set: ["fin", "urg"], unset: [] } }, then: "drop" },
|
|
73
|
+
{
|
|
74
|
+
description: "Bad flags Xmas scan",
|
|
75
|
+
if: { protocol: "tcp", tcp_flags: { set: ["fin", "syn", "psh", "urg"], unset: [] } },
|
|
76
|
+
then: "drop",
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/** Values every network writes the same way, each from a public standard. A global: write `presets.bogons`, with no import. */
|
|
81
|
+
export const presets = {
|
|
82
|
+
/**
|
|
83
|
+
* Address blocks that never appear as a source or destination on the internet: the IANA IPv4 and IPv6 Special-Purpose Address
|
|
84
|
+
* Registries (RFC 6890), each block with the RFC that reserves it. Pick the ones your filters need: `presets.bogons.ipv4.loopback`.
|
|
85
|
+
*/
|
|
86
|
+
bogons,
|
|
87
|
+
|
|
88
|
+
/** The IPv4 private ranges of RFC 1918, in address order. */
|
|
89
|
+
rfc1918: [bogons.ipv4.private10, bogons.ipv4.private172, bogons.ipv4.private192] as const,
|
|
90
|
+
|
|
91
|
+
/** The default route of each family: every destination, `0.0.0.0/0` (RFC 4632) and `::/0` (RFC 4291). */
|
|
92
|
+
default_routes: { ipv4: "0.0.0.0/0", ipv6: "::/0" } as const satisfies Record<"ipv4" | "ipv6", Prefix>,
|
|
93
|
+
|
|
94
|
+
/** Well-known ports, by the service names of the IANA Service Name and Transport Protocol Port Number Registry (RFC 6335). */
|
|
95
|
+
ports: {
|
|
96
|
+
/** DNS, RFC 1035. */
|
|
97
|
+
dns: 53,
|
|
98
|
+
/** DHCP and BOOTP server, RFC 2131. */
|
|
99
|
+
dhcp_server: 67,
|
|
100
|
+
/** DHCP and BOOTP client, RFC 2131. */
|
|
101
|
+
dhcp_client: 68,
|
|
102
|
+
/** NTP, RFC 5905. */
|
|
103
|
+
ntp: 123,
|
|
104
|
+
/** SNMP, RFC 3411. */
|
|
105
|
+
snmp: 161,
|
|
106
|
+
/** BGP, RFC 4271. */
|
|
107
|
+
bgp: 179,
|
|
108
|
+
/** Windows networking: MS-RPC endpoint mapper (135), NetBIOS (137 to 139, RFC 1001 and RFC 1002) and SMB (445), as IANA lists them. */
|
|
109
|
+
windows_networking: [135, { min: 137, max: 139 }, 445],
|
|
110
|
+
} as const satisfies Record<string, Ports>,
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Route policy rules rejecting what no neighbor should send, for an import, or as a policy every import calls (MANRS action 1,
|
|
114
|
+
* filtering). In order: IPv4 shorter than /8 and longer than /24, IPv6 shorter than /16 and longer than /48: too specific for the
|
|
115
|
+
* global table (RFC 7454, section 6.1.3), or wider than any block one network holds. Then routes RPKI finds invalid (RFC 6811).
|
|
116
|
+
* A policy of its own (`"common-sanity": presets.bgp_sanity`), or placed with `merge`: `merge([presets.bgp_sanity, own_rule])`.
|
|
117
|
+
*/
|
|
118
|
+
bgp_sanity,
|
|
119
|
+
|
|
120
|
+
/** Firewall rules dropping TCP flag combinations no packet that follows TCP (RFC 9293) carries, for a forward chain. Place them with `merge`. */
|
|
121
|
+
bad_tcp_flags,
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* UDP services that answer a small request with a much larger reply, which attackers use to reflect a flood at a victim: the ports
|
|
125
|
+
* of CISA's alert TA14-017A, "UDP-Based Amplification Attacks", plus six that are widely abused the same way but not in it: SLP,
|
|
126
|
+
* MS-SQL, ARD, CoAP, Ubiquiti and DVR. A reflected flood arrives from these. Pick the ports a rule needs by name: `src_port: [presets.udp_amplifiers.qotd, presets.udp_amplifiers.chargen]`.
|
|
127
|
+
*/
|
|
128
|
+
udp_amplifiers: {
|
|
129
|
+
qotd: 17,
|
|
130
|
+
chargen: 19,
|
|
131
|
+
dns: 53,
|
|
132
|
+
portmap: 111,
|
|
133
|
+
ntp: 123,
|
|
134
|
+
netbios: 137,
|
|
135
|
+
snmp: 161,
|
|
136
|
+
cldap: 389,
|
|
137
|
+
slp: 427,
|
|
138
|
+
rip: 520,
|
|
139
|
+
ms_sql: 1434,
|
|
140
|
+
ssdp: 1900,
|
|
141
|
+
ard: 3283,
|
|
142
|
+
ws_discovery: 3702,
|
|
143
|
+
mdns: 5353,
|
|
144
|
+
coap: 5683,
|
|
145
|
+
ubiquiti: 10001,
|
|
146
|
+
memcached: 11211,
|
|
147
|
+
dvr: 37810,
|
|
148
|
+
} as const,
|
|
149
|
+
};
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// Where packets go: `routing.tables`, `.vrfs`, `.policy_routing`, `.static_routes`, `.bgp`, `.ospf`, `.bfd`, `.rpki`, `.policies`, `.prefix_sets`.
|
|
2
|
+
|
|
3
|
+
import type { Family, HostAddress, IP, Named, Ordered, Prefix, Secret } from "./common";
|
|
4
|
+
import type { CommunitiesRule, NeighborCommunities } from "./communities";
|
|
5
|
+
import { ACTIONS, CALL_RULE, closest, COMMUNITIES_EXPORT, COMMUNITIES_IMPORT, ROUTE_MATCH, ROUTE_RULE, ROUTE_SET } from "./keys";
|
|
6
|
+
import type { AnyFacts, NetworkFacts } from "./network";
|
|
7
|
+
|
|
8
|
+
/** A route the device has without learning it. `I` is the device's ports and interfaces, `F` its VRFs, `T` its tables. */
|
|
9
|
+
export type StaticRoute<I extends string, F extends string, T extends string> = {
|
|
10
|
+
/** Destination. */
|
|
11
|
+
prefix: Prefix;
|
|
12
|
+
/** Next-hop address, alone or with its length. */
|
|
13
|
+
next_hop?: HostAddress;
|
|
14
|
+
/** One of the device's ports or interfaces: the next hop, for a link without a next-hop address. */
|
|
15
|
+
interface?: I;
|
|
16
|
+
/** Discard matching traffic. The usual way to originate an aggregate. */
|
|
17
|
+
blackhole?: true;
|
|
18
|
+
/** Withdraw the route while BFD finds `next_hop` dead, and put it back once `next_hop` answers again. Needs `next_hop`. */
|
|
19
|
+
bfd?: true;
|
|
20
|
+
/** Preference against other routes to the same prefix, 1 to 255. Lower wins. 1 when absent. */
|
|
21
|
+
distance?: number;
|
|
22
|
+
/** One of `routing.vrfs`: the VRF the route is in. A route has a `vrf` or a `routing_table`, not both. */
|
|
23
|
+
vrf?: F;
|
|
24
|
+
/** One of `routing.tables`: the table the route is in. The main table when absent. A route has a `vrf` or a `routing_table`, not both. */
|
|
25
|
+
routing_table?: T | "main";
|
|
26
|
+
/** Why the route exists. */
|
|
27
|
+
description?: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* What a BGP neighbor is to us, with the roles RFC 9234 defines: `provider`, a transit we buy from; `customer`, one we sell transit to; `peer`, one we exchange our own routes with; `rs`, an exchange's route server; `rs-client`, a network using our route server.
|
|
32
|
+
*/
|
|
33
|
+
export type BgpRole = "provider" | "customer" | "peer" | "rs" | "rs-client";
|
|
34
|
+
|
|
35
|
+
/** Settings a BGP neighbor can set itself or take from its group. `P` is the device's policies, `T` its tables, `Net` what the network writes. */
|
|
36
|
+
export type NeighborSettings<P extends string, T extends string, Net extends NetworkFacts = AnyFacts> = {
|
|
37
|
+
/** The neighbor's AS. Our own AS makes the session internal. */
|
|
38
|
+
remote_as?: number;
|
|
39
|
+
/** Who the neighbor is. */
|
|
40
|
+
description?: string;
|
|
41
|
+
/** Source address for the session, alone or with its length. The outgoing interface's address when absent. */
|
|
42
|
+
local_address?: HostAddress;
|
|
43
|
+
/** One of `routing.policies`: the policy for routes learned. Nothing is accepted when absent. */
|
|
44
|
+
import_policy?: P;
|
|
45
|
+
/** One of `routing.policies`: the policy for routes announced. Nothing is announced when absent. */
|
|
46
|
+
export_policy?: P;
|
|
47
|
+
/** What the neighbor is to us, such as `provider` for a transit. Both sides then reject a route leak, as RFC 9234 describes. */
|
|
48
|
+
role?: BgpRole;
|
|
49
|
+
/** The neighbor is more than one hop away. */
|
|
50
|
+
multihop?: boolean;
|
|
51
|
+
/** TCP MD5 password shared with the neighbor. */
|
|
52
|
+
password?: Secret;
|
|
53
|
+
/** Close the session when the neighbor announces more prefixes than this. It stays closed until cleared by hand, on RouterOS with `/routing bgp session clear <session> flag=limit-exceeded`. */
|
|
54
|
+
max_prefixes?: number;
|
|
55
|
+
/** Seconds without a message before the session is declared down, 3 or more. */
|
|
56
|
+
hold_time?: number;
|
|
57
|
+
/** Seconds between keepalive messages. */
|
|
58
|
+
keepalive?: number;
|
|
59
|
+
/** Address families exchanged. The family of the neighbor's address when absent. */
|
|
60
|
+
address_families?: readonly Family[];
|
|
61
|
+
/** Wait for the neighbor to connect instead of connecting to it. */
|
|
62
|
+
passive?: boolean;
|
|
63
|
+
/** Detect a dead link in under a second with BFD. */
|
|
64
|
+
bfd?: boolean;
|
|
65
|
+
/** One of `routing.tables`: the table the routes learned are installed in. The main table when absent. On RouterOS the session also announces from that table, so each prefix of `originated_prefixes` it announces needs a route in the table too, and a running session takes a new table only when it restarts. */
|
|
66
|
+
routing_table?: T | "main";
|
|
67
|
+
/**
|
|
68
|
+
* The groups of the network's `communities` it is in, and its own blackhole community, for the `{ communities }` rules of its
|
|
69
|
+
* policies: `{ groups: ["transit"], blackhole: "64501:666" }`. Only Circuit reads it. Allowed only when the network writes `communities`.
|
|
70
|
+
*/
|
|
71
|
+
communities?: Net["communities"] extends true ? NeighborCommunities<Net["groups"]> : "allowed only when circuit.ts writes communities";
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** One BGP session. Settings it does not set come from its neighbor group. */
|
|
75
|
+
export type Neighbor<P extends string, G extends string, T extends string, Net extends NetworkFacts = AnyFacts> = NeighborSettings<
|
|
76
|
+
P,
|
|
77
|
+
T,
|
|
78
|
+
Net
|
|
79
|
+
> & {
|
|
80
|
+
/** The neighbor's address. */
|
|
81
|
+
address: IP;
|
|
82
|
+
/** One of `routing.bgp.neighbor_groups`: the group this neighbor takes a setting from when it does not set that setting itself. */
|
|
83
|
+
neighbor_group?: G;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** BGP on this device. `G` is the device's neighbor groups. */
|
|
87
|
+
export type Bgp<P extends string, G extends string, T extends string, Net extends NetworkFacts = AnyFacts> = {
|
|
88
|
+
/** Our AS number. The network's `asn` when absent: the model needs one here, so Circuit writes the network's own. */
|
|
89
|
+
asn?: number;
|
|
90
|
+
/** Prefixes this device originates. Each needs a route, for example a static blackhole. Export policy still decides who receives them. */
|
|
91
|
+
originated_prefixes?: readonly Prefix[];
|
|
92
|
+
/** Settings shared by several neighbors, keyed by group name. */
|
|
93
|
+
neighbor_groups?: Named<G, NeighborSettings<P, T, Net>>;
|
|
94
|
+
/** Sessions, keyed by name. */
|
|
95
|
+
neighbors?: Record<string, Neighbor<P, NoInfer<G>, T, Net>>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** How fast BFD notices a dead neighbor: after `multiplier` packets in a row are missed, so in `interval` times `multiplier` milliseconds. */
|
|
99
|
+
export type Bfd = {
|
|
100
|
+
/** Milliseconds between the packets this device sends, and the shortest gap it asks of the neighbor. */
|
|
101
|
+
interval: number;
|
|
102
|
+
/** How many packets in a row may be missed before the neighbor is declared down. */
|
|
103
|
+
multiplier: number;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/** A route origin validation server, RFC 8210. */
|
|
107
|
+
export type RpkiServer = {
|
|
108
|
+
/** Where the validator listens. */
|
|
109
|
+
address: IP;
|
|
110
|
+
/** Its RTR port, often 323 or 8282. */
|
|
111
|
+
port: number;
|
|
112
|
+
/** Who runs it, or why it is here. */
|
|
113
|
+
description?: string;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** OSPF on one interface. */
|
|
117
|
+
export type OspfInterface = {
|
|
118
|
+
/** Cost of sending through this interface, 1 to 65535. Lower is preferred. Derived from speed when absent. */
|
|
119
|
+
cost?: number;
|
|
120
|
+
/** Advertise the interface's network but form no adjacency on it. */
|
|
121
|
+
passive?: boolean;
|
|
122
|
+
/** How the link is treated. Broadcast when absent. */
|
|
123
|
+
network?: "broadcast" | "point-to-point";
|
|
124
|
+
/** Detect a dead neighbor in under a second with BFD. */
|
|
125
|
+
bfd?: boolean;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** An OSPF area ID, four numbers with dots between them, for example `0.0.0.0` for the backbone. */
|
|
129
|
+
export type OspfAreaId = `${number}.${number}.${number}.${number}`;
|
|
130
|
+
|
|
131
|
+
/** OSPF, version 2 for IPv4 and version 3 for IPv6. */
|
|
132
|
+
export type Ospf<I extends string> = {
|
|
133
|
+
/** Which versions run. IPv4 when absent. */
|
|
134
|
+
address_families?: readonly Family[];
|
|
135
|
+
/** Areas keyed by area ID, for example `0.0.0.0`, each with the interfaces in it. */
|
|
136
|
+
areas: Record<
|
|
137
|
+
OspfAreaId,
|
|
138
|
+
{
|
|
139
|
+
/** Some of the device's ports and interfaces, each with its OSPF settings. */
|
|
140
|
+
interfaces: Named<I, OspfInterface>;
|
|
141
|
+
}
|
|
142
|
+
>;
|
|
143
|
+
/** Routes from other sources announced into OSPF. */
|
|
144
|
+
redistribute?: readonly ("connected" | "static")[];
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/** A routing table beside the main one. It holds no interfaces: routes go in from static routes and BGP neighbors that name it in `routing_table`, and `routing.policy_routing` sends traffic to it. */
|
|
148
|
+
export type RoutingTable = {
|
|
149
|
+
/** What the table is for. */
|
|
150
|
+
description?: string;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** A separate routing table, with the interfaces that route in it. */
|
|
154
|
+
export type Vrf = {
|
|
155
|
+
/** What the VRF is for. */
|
|
156
|
+
description?: string;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/** Traffic a routing rule matches: all of what is given. Every packet when absent. */
|
|
160
|
+
export type RoutingRuleMatch<I extends string> = {
|
|
161
|
+
/** Source prefix. */
|
|
162
|
+
src?: Prefix;
|
|
163
|
+
/** Destination prefix. */
|
|
164
|
+
dst?: Prefix;
|
|
165
|
+
/** One of the device's ports or interfaces: the traffic arrives on it. */
|
|
166
|
+
in_interface?: I;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/** One policy routing rule: traffic it matches is routed by `routing_table` instead of the main table. */
|
|
170
|
+
export type RoutingRule<I extends string, T extends string> = {
|
|
171
|
+
/** Why the rule exists. */
|
|
172
|
+
description?: string;
|
|
173
|
+
/** What the traffic must match. Every packet when absent, so such a rule is the last one. */
|
|
174
|
+
match?: RoutingRuleMatch<I>;
|
|
175
|
+
/** One of `routing.tables`, or `main`: the table that routes the traffic. Always written: choosing it is what the rule is for. */
|
|
176
|
+
routing_table: T | "main";
|
|
177
|
+
/** When `routing_table` has no route for a packet: `none` drops it as unroutable, `main` routes it by the main table instead. `none` when absent. Not for a rule whose table is `main`. */
|
|
178
|
+
fallback?: "none" | "main";
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/** A standard BGP community, `asn:value`, for example `64500:100`. Each part is 0 to 65535. */
|
|
182
|
+
export type StandardCommunity = `${number}:${number}`;
|
|
183
|
+
|
|
184
|
+
/** A large BGP community, `asn:function:parameter`, for example `64500:1:6939`. Each part is 0 to 4294967295. */
|
|
185
|
+
export type LargeCommunity = `${number}:${number}:${number}`;
|
|
186
|
+
|
|
187
|
+
/** A standard community to remove. `*` stands for any value of a part, as in `64500:*`. */
|
|
188
|
+
export type StandardCommunityPattern = `${number | "*"}:${number | "*"}`;
|
|
189
|
+
|
|
190
|
+
/** A large community to remove. `*` stands for any value of a part, as in `64500:*:*`. */
|
|
191
|
+
export type LargeCommunityPattern = `${number | "*"}:${number | "*"}:${number | "*"}`;
|
|
192
|
+
|
|
193
|
+
/** Prefixes listed here, or fetched from a registry by `refresh` and never typed by hand. */
|
|
194
|
+
export type PrefixSet =
|
|
195
|
+
| {
|
|
196
|
+
/** The prefixes, of either family or both: a list, or a map of named prefixes, which gives every value. Where the platform keeps one list per family, Circuit writes one of each. */
|
|
197
|
+
prefixes: readonly Prefix[] | Readonly<Record<string, Prefix>>;
|
|
198
|
+
}
|
|
199
|
+
| {
|
|
200
|
+
/** Where the prefixes are fetched from. */
|
|
201
|
+
source: {
|
|
202
|
+
/** The registry they come from. */
|
|
203
|
+
registry: "ripe-stat";
|
|
204
|
+
/** What to ask for, for example `AS64501`. */
|
|
205
|
+
query: string;
|
|
206
|
+
/** Which family to keep. */
|
|
207
|
+
family: Family;
|
|
208
|
+
};
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/** Conditions on a route. All must be true. An empty match is true for every route. `S` is the device's prefix sets. */
|
|
212
|
+
export type RouteMatch<S extends string> = {
|
|
213
|
+
/** Only routes of this family. */
|
|
214
|
+
family?: Family;
|
|
215
|
+
/** Exactly this prefix. */
|
|
216
|
+
prefix?: Prefix;
|
|
217
|
+
/** One of `routing.prefix_sets`: any prefix in it. */
|
|
218
|
+
prefix_set?: S;
|
|
219
|
+
/** Prefix length within this range, inclusive, 0 to 128. */
|
|
220
|
+
prefix_length?: {
|
|
221
|
+
/** The shortest length. */
|
|
222
|
+
min?: number;
|
|
223
|
+
/** The longest length. */
|
|
224
|
+
max?: number;
|
|
225
|
+
};
|
|
226
|
+
/** AS path length within this range, inclusive. Every AS counts, prepends included. */
|
|
227
|
+
as_path_length?: {
|
|
228
|
+
/** The shortest path. */
|
|
229
|
+
min?: number;
|
|
230
|
+
/** The longest path. */
|
|
231
|
+
max?: number;
|
|
232
|
+
};
|
|
233
|
+
/** The AS path holds this AS anywhere: the route came from it or through it. */
|
|
234
|
+
as_path_contains?: number;
|
|
235
|
+
/** The route's origin validation state. */
|
|
236
|
+
rpki?: "valid" | "invalid" | "not-found";
|
|
237
|
+
/** Has this standard community, `asn:value`. */
|
|
238
|
+
standard_community?: StandardCommunity;
|
|
239
|
+
/** Has this large community, `asn:function:parameter`. */
|
|
240
|
+
large_community?: LargeCommunity;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/** Changes to a route. In community lists to remove, `*` stands for any value of a part, as in `64500:*:*`. */
|
|
244
|
+
export type RouteSet = {
|
|
245
|
+
/** Preference among routes to the same prefix within our AS. Higher wins. */
|
|
246
|
+
local_pref?: number;
|
|
247
|
+
/** Multi-exit discriminator offered to the neighbor. Lower wins. */
|
|
248
|
+
med?: number;
|
|
249
|
+
/** Prepend our own AS this many times, 1 to 16, to make the path look longer. */
|
|
250
|
+
prepend?: number;
|
|
251
|
+
/** Replace the route's standard communities with these. */
|
|
252
|
+
standard_communities?: readonly StandardCommunity[];
|
|
253
|
+
/** Add these standard communities. */
|
|
254
|
+
add_standard_communities?: readonly StandardCommunity[];
|
|
255
|
+
/** Remove standard communities matching these. A part may be `*`. */
|
|
256
|
+
remove_standard_communities?: readonly StandardCommunityPattern[];
|
|
257
|
+
/** Replace the route's large communities with these. */
|
|
258
|
+
large_communities?: readonly LargeCommunity[];
|
|
259
|
+
/** Add these large communities. */
|
|
260
|
+
add_large_communities?: readonly LargeCommunity[];
|
|
261
|
+
/** Remove large communities matching these. A part may be `*`. */
|
|
262
|
+
remove_large_communities?: readonly LargeCommunityPattern[];
|
|
263
|
+
/** Next hop for the route. */
|
|
264
|
+
next_hop?: IP;
|
|
265
|
+
/** Source address for traffic the device itself sends to the route, alone or with its length. Applies to routes of the same family as the address. */
|
|
266
|
+
preferred_source?: HostAddress;
|
|
267
|
+
/** Drop traffic to the route on this device, instead of forwarding it. */
|
|
268
|
+
blackhole?: true;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/** A route policy rule that runs another policy, and holds nothing else: `{ call: "exchange-import" }`. */
|
|
272
|
+
export type CallRule<P extends string> = {
|
|
273
|
+
/** One of `routing.policies`, run here. A route it accepts or rejects stops there; any other goes on, with what it set. To call it for some routes only, put the condition inside that policy. */
|
|
274
|
+
call: P;
|
|
275
|
+
/** A call rule holds nothing else. */
|
|
276
|
+
communities?: never;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/** A route policy rule on routes: which ones (`if`), what to change (`set`), and what then (`then`). It has `then`, `set`, or both. */
|
|
280
|
+
export type RouteRule<S extends string> = {
|
|
281
|
+
/** Shown beside the rule on the device. */
|
|
282
|
+
description?: string;
|
|
283
|
+
/** Which routes the rule applies to. Every route when absent. */
|
|
284
|
+
if?: RouteMatch<S>;
|
|
285
|
+
/** What to change on a route that matches. Without `then`, the route goes on to the next rule, changed. */
|
|
286
|
+
set?: RouteSet;
|
|
287
|
+
/** What happens to a route that matches, after `set`: accepted or rejected, and the policy ends for it. Without `then`, it goes on to the next rule. */
|
|
288
|
+
then?: "accept" | "reject";
|
|
289
|
+
/** A rule that calls another policy holds nothing else. */
|
|
290
|
+
call?: never;
|
|
291
|
+
/** A communities rule holds nothing else. */
|
|
292
|
+
communities?: never;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* One step of a route policy, plain data, the condition first:
|
|
297
|
+
*
|
|
298
|
+
* - `{ if: { prefix_set: "bogons" }, then: "reject" }` rejects the routes that match. No `if`: every route, `{ then: "accept" }`.
|
|
299
|
+
* - `{ if: { prefix: "0.0.0.0/0" }, set: { local_pref: 110 }, then: "accept" }` changes them, then accepts them.
|
|
300
|
+
* - `{ if: { ... }, set: { local_pref: 300 } }` changes them and goes on to the next rule.
|
|
301
|
+
* - `{ call: "other-policy" }` runs one of `routing.policies`, and holds nothing else.
|
|
302
|
+
* - `{ communities: "import" }` or `{ communities: "export" }`: the rules the network's `communities` mean here, which Circuit writes
|
|
303
|
+
* for the neighbors that use the policy. Allowed only when the network writes `communities`.
|
|
304
|
+
*
|
|
305
|
+
* The model keeps each as written.
|
|
306
|
+
*/
|
|
307
|
+
export type PolicyRule<P extends string, S extends string, Net extends NetworkFacts = AnyFacts> =
|
|
308
|
+
| CallRule<P>
|
|
309
|
+
| RouteRule<S>
|
|
310
|
+
| (Net["communities"] extends true
|
|
311
|
+
? CommunitiesRule<Net["requests"]>
|
|
312
|
+
: { communities: "allowed only when circuit.ts writes communities" });
|
|
313
|
+
|
|
314
|
+
/** What a route policy rule, its `if` and its `set` may hold, at run time: from keys.ts, which the types above check. */
|
|
315
|
+
const RULE_KEYS = Object.keys(ROUTE_RULE);
|
|
316
|
+
const MATCH_KEYS = Object.keys(ROUTE_MATCH);
|
|
317
|
+
const SET_KEYS = Object.keys(ROUTE_SET);
|
|
318
|
+
const COMMUNITIES_RULE: Record<string, string[]> = {
|
|
319
|
+
import: Object.keys(COMMUNITIES_IMPORT).filter((k) => k !== "communities"),
|
|
320
|
+
export: Object.keys(COMMUNITIES_EXPORT).filter((k) => k !== "communities"),
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
/** Keys of `written` not in `takes`, each with "did you mean" where one is close: `protocl (did you mean protocol?)`. */
|
|
324
|
+
const wrongKeys = (written: string[], takes: string[]) =>
|
|
325
|
+
written
|
|
326
|
+
.filter((k) => !takes.includes(k))
|
|
327
|
+
.map((k) => {
|
|
328
|
+
const hint = closest(k, takes);
|
|
329
|
+
return hint ? `${k} (did you mean ${hint}?)` : k;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
/** What is wrong with a route policy rule as written, or nothing. */
|
|
333
|
+
export function policyRuleProblem(rule: unknown): string | undefined {
|
|
334
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule))
|
|
335
|
+
return `${JSON.stringify(rule)} is no rule. A rule is { if, set, then }, or { call }.`;
|
|
336
|
+
const r = rule as Record<string, unknown>;
|
|
337
|
+
const keys = Object.keys(r);
|
|
338
|
+
if ("communities" in r) {
|
|
339
|
+
const takes = COMMUNITIES_RULE[r.communities as string];
|
|
340
|
+
if (!takes) return `communities is import or export, not ${JSON.stringify(r.communities)}.`;
|
|
341
|
+
const more = wrongKeys(
|
|
342
|
+
keys.filter((k) => k !== "communities"),
|
|
343
|
+
takes
|
|
344
|
+
);
|
|
345
|
+
return more.length ? `a communities ${r.communities} rule has no ${more.join(", ")}. It takes: ${takes.join(", ")}.` : undefined;
|
|
346
|
+
}
|
|
347
|
+
if ("call" in r) {
|
|
348
|
+
if (typeof r.call !== "string") return "call takes the name of a policy.";
|
|
349
|
+
const more = keys.filter((k) => !Object.hasOwn(CALL_RULE, k));
|
|
350
|
+
return more.length ? `a call rule holds nothing else, but this one has ${more.join(", ")}.` : undefined;
|
|
351
|
+
}
|
|
352
|
+
const wrong = wrongKeys(keys, RULE_KEYS);
|
|
353
|
+
if (wrong.length) return `a rule has no ${wrong.join(", ")}. It takes: ${RULE_KEYS.join(", ")}, or call alone.`;
|
|
354
|
+
const then: readonly unknown[] = ACTIONS.policy;
|
|
355
|
+
if (r.then !== undefined && !then.includes(r.then)) {
|
|
356
|
+
const hint = typeof r.then === "string" ? closest(r.then, ACTIONS.policy) : undefined;
|
|
357
|
+
return `then is accept or reject, not ${JSON.stringify(r.then)}.${hint ? ` Did you mean ${hint}?` : ""}`;
|
|
358
|
+
}
|
|
359
|
+
if (r.then === undefined && r.set === undefined) return "the rule does nothing: it needs then, set, or both.";
|
|
360
|
+
const match = wrongKeys(Object.keys((r.if as object | undefined) ?? {}), MATCH_KEYS);
|
|
361
|
+
if (match.length) return `if has no ${match.join(", ")}. It takes: ${MATCH_KEYS.join(", ")}.`;
|
|
362
|
+
const set = wrongKeys(Object.keys((r.set as object | undefined) ?? {}), SET_KEYS);
|
|
363
|
+
return set.length ? `set has no ${set.join(", ")}. It takes: ${SET_KEYS.join(", ")}.` : undefined;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Where packets go. The type parameters are the names the device declares: `P` policies, `S` prefix sets, `G` neighbor groups, `T` tables, `F` VRFs; `I` is its ports and interfaces. `Part` is true in a part, where BGP holds only what the part adds. `Net` is what the network writes.
|
|
368
|
+
*/
|
|
369
|
+
export type Routing<
|
|
370
|
+
P extends string,
|
|
371
|
+
S extends string,
|
|
372
|
+
G extends string,
|
|
373
|
+
T extends string,
|
|
374
|
+
F extends string,
|
|
375
|
+
I extends string,
|
|
376
|
+
Part extends boolean = false,
|
|
377
|
+
Net extends NetworkFacts = AnyFacts,
|
|
378
|
+
> = {
|
|
379
|
+
/** Identifies this router to its neighbors. */
|
|
380
|
+
router_id?: IP;
|
|
381
|
+
/** Routing tables beside the main one, keyed by name. Not `main`, and not the name of a VRF. */
|
|
382
|
+
tables?: Named<T, RoutingTable>;
|
|
383
|
+
/** Separate routing tables with the interfaces that route in them, keyed by name. */
|
|
384
|
+
vrfs?: Named<F, Vrf>;
|
|
385
|
+
/** Rules in order that route matching traffic by another table, such as traffic from one block by its own transits' default. The first that matches decides; traffic no rule matches is routed by the main table. */
|
|
386
|
+
policy_routing?: Ordered<RoutingRule<I, NoInfer<T>>>;
|
|
387
|
+
/** Routes the device has without learning them. A set: order means nothing. */
|
|
388
|
+
static_routes?: readonly StaticRoute<I, NoInfer<F>, NoInfer<T>>[];
|
|
389
|
+
/** BGP sessions and what they exchange. In a part, only what the part adds, such as its neighbor groups and neighbors. */
|
|
390
|
+
bgp?: Part extends true ? Partial<Bgp<NoInfer<P>, G, NoInfer<T>, Net>> : Bgp<NoInfer<P>, G, NoInfer<T>, Net>;
|
|
391
|
+
/** OSPF areas and interfaces. */
|
|
392
|
+
ospf?: Ospf<I>;
|
|
393
|
+
/** BFD timers for every BFD session on the device: BGP, OSPF and static routes. The platform's own when absent; Junos has none, so Circuit sends every 300 ms there. Both ends of a link should agree. */
|
|
394
|
+
bfd?: Bfd;
|
|
395
|
+
/** Validators answering route origin validation. */
|
|
396
|
+
rpki?: readonly RpkiServer[];
|
|
397
|
+
/** Route policies, keyed by name. Each is rules in order, written whole in one place: the first runs first, and a route no rule accepts is rejected. */
|
|
398
|
+
policies?: Named<P, Ordered<PolicyRule<NoInfer<P>, NoInfer<S>, Net>>>;
|
|
399
|
+
/** Prefix sets that policies match on, keyed by name. */
|
|
400
|
+
prefix_sets?: Named<S, PrefixSet>;
|
|
401
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// What the box serves to hosts: `services.dhcp_server`, `.dhcp_relay`, `.dns_server`, `.ntp_server`.
|
|
2
|
+
|
|
3
|
+
import type { IP, MacAddress, Prefix } from "./common";
|
|
4
|
+
|
|
5
|
+
/** An address a DHCP server keeps for one client. */
|
|
6
|
+
export type Reservation = {
|
|
7
|
+
/** The address it keeps. */
|
|
8
|
+
address: IP;
|
|
9
|
+
/** The client's MAC address. */
|
|
10
|
+
mac: MacAddress;
|
|
11
|
+
/** Whose address it is. */
|
|
12
|
+
description?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** A DHCP server for one network. */
|
|
16
|
+
export type DhcpServer<I extends string> = {
|
|
17
|
+
/** One of the device's ports or interfaces: where it answers. */
|
|
18
|
+
interface: I;
|
|
19
|
+
/** The network it serves. */
|
|
20
|
+
network: Prefix;
|
|
21
|
+
/** Default gateway given to clients. */
|
|
22
|
+
gateway: IP;
|
|
23
|
+
/** Resolvers given to clients. */
|
|
24
|
+
dns?: readonly IP[];
|
|
25
|
+
/** First and last address given to clients. */
|
|
26
|
+
pool: readonly [IP, IP];
|
|
27
|
+
/** Seconds a lease lasts. */
|
|
28
|
+
lease_time?: number;
|
|
29
|
+
/** Addresses fixed to a MAC. */
|
|
30
|
+
reservations?: readonly Reservation[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Forward DHCP requests heard on an interface to servers elsewhere. */
|
|
34
|
+
export type DhcpRelay<I extends string> = {
|
|
35
|
+
/** One of the device's ports or interfaces: where requests are heard. */
|
|
36
|
+
interface: I;
|
|
37
|
+
/** Where they are forwarded, in order of preference. */
|
|
38
|
+
servers: readonly IP[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** What the device serves to hosts. `I` is the device's ports and interfaces. */
|
|
42
|
+
export type Services<I extends string> = {
|
|
43
|
+
/** DHCP servers, keyed by name, one per network. */
|
|
44
|
+
dhcp_server?: Record<string, DhcpServer<I>>;
|
|
45
|
+
/** DHCP relays, keyed by name. */
|
|
46
|
+
dhcp_relay?: Record<string, DhcpRelay<I>>;
|
|
47
|
+
/** Answer DNS queries from others, from `system.dns.servers`. Off when absent. */
|
|
48
|
+
dns_server?: true;
|
|
49
|
+
/** Answer time queries from others. Off when absent. */
|
|
50
|
+
ntp_server?: true;
|
|
51
|
+
};
|