@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.
Files changed (256) hide show
  1. package/README.md +115 -37
  2. package/package.json +21 -58
  3. package/src/checks/across.ts +402 -0
  4. package/src/checks/built-in.ts +256 -0
  5. package/src/checks/device.ts +436 -0
  6. package/src/checks/index.ts +53 -0
  7. package/src/cli/advisories.ts +108 -0
  8. package/src/cli/app.ts +71 -0
  9. package/src/cli/editor-types.generated.ts +30 -0
  10. package/src/cli/editor-types.ts +69 -0
  11. package/src/cli/format.ts +124 -0
  12. package/src/cli/git-hooks.ts +79 -0
  13. package/src/cli/index.ts +350 -1170
  14. package/src/cli/migrate.ts +554 -0
  15. package/src/cli/project-commands.ts +656 -0
  16. package/src/cli/releases.ts +374 -0
  17. package/src/cli/remember.ts +339 -0
  18. package/src/cli/rename.ts +27 -33
  19. package/src/cli/secrets-password.ts +60 -0
  20. package/src/cli/secrets.ts +258 -0
  21. package/src/cli/setup.ts +300 -0
  22. package/src/cli/signing.ts +23 -0
  23. package/src/cli/text-editor.ts +268 -0
  24. package/src/cli/ui.ts +298 -0
  25. package/src/cli/version.ts +2 -0
  26. package/src/cli/versions.ts +158 -0
  27. package/src/config/address.ts +102 -0
  28. package/src/config/circuit.d.ts +30 -0
  29. package/src/config/common.ts +65 -0
  30. package/src/config/communities.ts +479 -0
  31. package/src/config/define.ts +684 -0
  32. package/src/config/device.ts +164 -0
  33. package/src/config/firewall.ts +150 -0
  34. package/src/config/index.ts +22 -0
  35. package/src/{schema → config}/interfaces.ts +121 -85
  36. package/src/config/keys.ts +434 -0
  37. package/src/config/management.ts +61 -0
  38. package/src/config/merge.ts +335 -0
  39. package/src/config/models.ts +70 -0
  40. package/src/config/monitoring.ts +64 -0
  41. package/src/config/network.ts +180 -0
  42. package/src/config/presets.ts +149 -0
  43. package/src/config/routing.ts +401 -0
  44. package/src/config/services.ts +51 -0
  45. package/src/config/switching.ts +84 -0
  46. package/src/config/system.ts +103 -0
  47. package/src/{schema → config}/time-zones.ts +1 -1
  48. package/src/core/address.ts +1 -1
  49. package/src/core/cabling.ts +8 -43
  50. package/src/core/graph.ts +40 -0
  51. package/src/{integrations → core}/registries.ts +5 -3
  52. package/src/core/secrets-file.ts +539 -0
  53. package/src/core/secrets.ts +83 -64
  54. package/src/core/topology.ts +103 -146
  55. package/src/core/utils.ts +12 -1
  56. package/src/model.ts +83 -0
  57. package/src/platforms/derive.ts +64 -37
  58. package/src/platforms/finding.ts +9 -0
  59. package/src/platforms/fortios/firewall.ts +76 -64
  60. package/src/platforms/fortios/index.ts +189 -125
  61. package/src/platforms/fortios/names.ts +51 -18
  62. package/src/platforms/fortios/render.ts +69 -62
  63. package/src/platforms/fortios/routing.ts +89 -58
  64. package/src/platforms/fortios/vdoms.ts +47 -16
  65. package/src/platforms/junos/TRAPS.md +2 -2
  66. package/src/platforms/junos/bgp.ts +63 -42
  67. package/src/platforms/junos/index.ts +122 -89
  68. package/src/platforms/junos/render.ts +139 -104
  69. package/src/platforms/platform.ts +10 -8
  70. package/src/platforms/raisecom-ros/TRAPS.md +1 -1
  71. package/src/platforms/raisecom-ros/index.ts +89 -70
  72. package/src/platforms/raisecom-ros/render.ts +49 -45
  73. package/src/platforms/routeros/TRAPS.md +3 -3
  74. package/src/platforms/routeros/index.ts +121 -60
  75. package/src/platforms/routeros/render.ts +214 -195
  76. package/src/platforms/vrp/TRAPS.md +1 -1
  77. package/src/platforms/vrp/index.ts +74 -50
  78. package/src/platforms/vrp/render.ts +67 -56
  79. package/src/project/project.ts +725 -0
  80. package/src/studio/app/env.d.ts +9 -0
  81. package/src/studio/app/index.html +18 -0
  82. package/src/studio/app/src/App.vue +95 -0
  83. package/src/studio/app/src/components/AddressesView.vue +38 -0
  84. package/src/studio/app/src/components/CablesView.vue +29 -0
  85. package/src/studio/app/src/components/CompareView.vue +160 -0
  86. package/src/studio/app/src/components/DataTable.vue +49 -0
  87. package/src/studio/app/src/components/DeviceView.vue +237 -0
  88. package/src/studio/app/src/components/DevicesView.vue +32 -0
  89. package/src/studio/app/src/components/Faceplate.vue +75 -0
  90. package/src/studio/app/src/components/GraphView.vue +282 -0
  91. package/src/studio/app/src/components/LineDiff.vue +24 -0
  92. package/src/studio/app/src/components/ProblemsView.vue +42 -0
  93. package/src/studio/app/src/components/SearchView.vue +72 -0
  94. package/src/studio/app/src/components/SecretsView.vue +112 -0
  95. package/src/studio/app/src/components/SessionsView.vue +35 -0
  96. package/src/studio/app/src/components/Settings.vue +95 -0
  97. package/src/studio/app/src/components/ToolsView.vue +118 -0
  98. package/src/studio/app/src/components/VlansView.vue +50 -0
  99. package/src/studio/app/src/lib/csv.ts +19 -0
  100. package/src/studio/{public/diff.js → app/src/lib/diff.ts} +40 -35
  101. package/src/studio/app/src/lib/format.ts +38 -0
  102. package/src/studio/app/src/lib/net.ts +68 -0
  103. package/src/studio/app/src/lib/secrets-crypto.ts +35 -0
  104. package/src/studio/app/src/lib/store.ts +71 -0
  105. package/src/studio/app/src/lib/tools.ts +217 -0
  106. package/src/studio/app/src/main.ts +9 -0
  107. package/src/studio/{public → app/src}/style.css +14 -4
  108. package/src/studio/app/tsconfig.json +17 -0
  109. package/src/studio/app/vite.config.ts +16 -0
  110. package/src/studio/assets.generated.ts +3 -0
  111. package/src/studio/data.ts +96 -188
  112. package/src/studio/git.ts +27 -43
  113. package/src/studio/server.ts +196 -107
  114. package/src/studio/types.ts +141 -0
  115. package/src/transport/console.ts +3 -3
  116. package/src/transport/ssh.ts +1 -1
  117. package/dist/chunks/index-3b343xva.js +0 -177
  118. package/dist/chunks/index-6wb41q67.js +0 -62
  119. package/dist/chunks/index-n7w3tg5y.js +0 -149
  120. package/dist/chunks/index-yzw86rc8.js +0 -921
  121. package/dist/chunks/new-w3zys5y4.js +0 -172
  122. package/dist/chunks/server-cxr9ynxc.js +0 -259
  123. package/dist/cli/index.js +0 -12922
  124. package/dist/helpers/index.js +0 -881
  125. package/dist/index.js +0 -28
  126. package/dist/types/core/address-space.d.ts +0 -62
  127. package/dist/types/core/address.d.ts +0 -44
  128. package/dist/types/core/cabling.d.ts +0 -41
  129. package/dist/types/core/cross-checks.d.ts +0 -28
  130. package/dist/types/core/define.d.ts +0 -79
  131. package/dist/types/core/project-files.d.ts +0 -12
  132. package/dist/types/core/schema-docs.d.ts +0 -25
  133. package/dist/types/core/secrets.d.ts +0 -62
  134. package/dist/types/core/topology.d.ts +0 -96
  135. package/dist/types/core/utils.d.ts +0 -10
  136. package/dist/types/core/validator.d.ts +0 -23
  137. package/dist/types/helpers/bgp.d.ts +0 -195
  138. package/dist/types/helpers/cables.d.ts +0 -69
  139. package/dist/types/helpers/checks.d.ts +0 -36
  140. package/dist/types/helpers/exchange.d.ts +0 -55
  141. package/dist/types/helpers/filters.d.ts +0 -51
  142. package/dist/types/helpers/index.d.ts +0 -14
  143. package/dist/types/helpers/route-server.d.ts +0 -52
  144. package/dist/types/helpers/upstreams.d.ts +0 -192
  145. package/dist/types/index.d.ts +0 -10
  146. package/dist/types/integrations/librenms.d.ts +0 -60
  147. package/dist/types/integrations/netbox.d.ts +0 -89
  148. package/dist/types/integrations/peeringdb.d.ts +0 -24
  149. package/dist/types/integrations/prometheus.d.ts +0 -24
  150. package/dist/types/integrations/registries.d.ts +0 -8
  151. package/dist/types/platforms/bgp-wait.d.ts +0 -24
  152. package/dist/types/platforms/catalog.d.ts +0 -109
  153. package/dist/types/platforms/derive.d.ts +0 -17
  154. package/dist/types/platforms/fortios/firewall.d.ts +0 -47
  155. package/dist/types/platforms/fortios/index.d.ts +0 -140
  156. package/dist/types/platforms/fortios/models.d.ts +0 -14
  157. package/dist/types/platforms/fortios/names.d.ts +0 -52
  158. package/dist/types/platforms/fortios/plan.d.ts +0 -114
  159. package/dist/types/platforms/fortios/render.d.ts +0 -77
  160. package/dist/types/platforms/fortios/routing.d.ts +0 -56
  161. package/dist/types/platforms/fortios/tree.d.ts +0 -37
  162. package/dist/types/platforms/fortios/vdoms.d.ts +0 -50
  163. package/dist/types/platforms/index.d.ts +0 -13
  164. package/dist/types/platforms/junos/bgp.d.ts +0 -44
  165. package/dist/types/platforms/junos/index.d.ts +0 -114
  166. package/dist/types/platforms/junos/models.d.ts +0 -14
  167. package/dist/types/platforms/junos/render.d.ts +0 -154
  168. package/dist/types/platforms/line-diff.d.ts +0 -67
  169. package/dist/types/platforms/platform.d.ts +0 -135
  170. package/dist/types/platforms/raisecom-ros/index.d.ts +0 -71
  171. package/dist/types/platforms/raisecom-ros/models.d.ts +0 -14
  172. package/dist/types/platforms/raisecom-ros/render.d.ts +0 -41
  173. package/dist/types/platforms/routeros/export.d.ts +0 -59
  174. package/dist/types/platforms/routeros/index.d.ts +0 -116
  175. package/dist/types/platforms/routeros/models.d.ts +0 -18
  176. package/dist/types/platforms/routeros/plan.d.ts +0 -58
  177. package/dist/types/platforms/routeros/render.d.ts +0 -250
  178. package/dist/types/platforms/vrp/index.d.ts +0 -83
  179. package/dist/types/platforms/vrp/models.d.ts +0 -12
  180. package/dist/types/platforms/vrp/render.d.ts +0 -96
  181. package/dist/types/schema/common.d.ts +0 -30
  182. package/dist/types/schema/device.d.ts +0 -126
  183. package/dist/types/schema/firewall.d.ts +0 -131
  184. package/dist/types/schema/index.d.ts +0 -8
  185. package/dist/types/schema/interfaces.d.ts +0 -205
  186. package/dist/types/schema/policy.d.ts +0 -107
  187. package/dist/types/schema/routing.d.ts +0 -161
  188. package/dist/types/schema/system.d.ts +0 -198
  189. package/dist/types/schema/time-zones.d.ts +0 -2
  190. package/dist/types/transport/console.d.ts +0 -115
  191. package/dist/types/transport/path-check.d.ts +0 -32
  192. package/dist/types/transport/ssh.d.ts +0 -27
  193. package/src/cli/graph-format.ts +0 -91
  194. package/src/cli/new.ts +0 -234
  195. package/src/core/address-space.ts +0 -320
  196. package/src/core/cross-checks.ts +0 -369
  197. package/src/core/define.ts +0 -124
  198. package/src/core/project-files.ts +0 -35
  199. package/src/core/schema-docs.ts +0 -174
  200. package/src/core/validator.ts +0 -508
  201. package/src/helpers/bgp.ts +0 -473
  202. package/src/helpers/cables.ts +0 -74
  203. package/src/helpers/checks.ts +0 -201
  204. package/src/helpers/exchange.ts +0 -123
  205. package/src/helpers/filters.ts +0 -107
  206. package/src/helpers/index.ts +0 -25
  207. package/src/helpers/route-server.ts +0 -212
  208. package/src/helpers/upstreams.ts +0 -369
  209. package/src/index.ts +0 -13
  210. package/src/integrations/librenms.ts +0 -291
  211. package/src/integrations/netbox.ts +0 -620
  212. package/src/integrations/peeringdb.ts +0 -173
  213. package/src/integrations/prometheus.ts +0 -90
  214. package/src/schema/common.ts +0 -41
  215. package/src/schema/device.ts +0 -145
  216. package/src/schema/firewall.ts +0 -140
  217. package/src/schema/index.ts +0 -8
  218. package/src/schema/policy.ts +0 -115
  219. package/src/schema/routing.ts +0 -173
  220. package/src/schema/system.ts +0 -203
  221. package/src/studio/public/app.js +0 -186
  222. package/src/studio/public/compare.js +0 -250
  223. package/src/studio/public/dom.js +0 -138
  224. package/src/studio/public/evaluate.js +0 -687
  225. package/src/studio/public/export.js +0 -130
  226. package/src/studio/public/graph.js +0 -1541
  227. package/src/studio/public/index.html +0 -83
  228. package/src/studio/public/net.js +0 -134
  229. package/src/studio/public/tables.js +0 -1289
  230. package/src/studio/public/tools.js +0 -610
  231. package/templates/colocation/checks.ts +0 -53
  232. package/templates/colocation/circuit.config.ts +0 -9
  233. package/templates/colocation/edge-01.ts +0 -133
  234. package/templates/colocation/routing.ts +0 -13
  235. package/templates/colocation/tenancy.ts +0 -76
  236. package/templates/colocation/tenants/acme.ts +0 -19
  237. package/templates/colocation/tenants/index.ts +0 -6
  238. package/templates/colocation/tor-01.ts +0 -33
  239. package/templates/edge-router/checks.ts +0 -20
  240. package/templates/edge-router/circuit.config.ts +0 -8
  241. package/templates/edge-router/edge-01.ts +0 -110
  242. package/templates/edge-router/routing.ts +0 -13
  243. package/templates/project/AGENTS.md +0 -51
  244. package/templates/project/CLAUDE.md +0 -1
  245. package/templates/project/README.md +0 -16
  246. package/templates/project/gitignore +0 -5
  247. package/templates/project/tsconfig.json +0 -13
  248. package/templates/single-site/checks.ts +0 -26
  249. package/templates/single-site/circuit.config.ts +0 -8
  250. package/templates/single-site/router.ts +0 -86
  251. package/templates/single-site/site.ts +0 -18
  252. package/templates/single-site/switch.ts +0 -34
  253. /package/src/studio/{public → app/public}/favicon.svg +0 -0
  254. /package/src/studio/{public → app/public}/special-gothic-latin-ext.woff2 +0 -0
  255. /package/src/studio/{public → app/public}/special-gothic-latin.woff2 +0 -0
  256. /package/src/studio/{public → app/public}/special-gothic-license.txt +0 -0
@@ -1,473 +0,0 @@
1
- import { Address } from "../core/address";
2
- import type { Community } from "../core/define";
3
- import type {
4
- Device,
5
- IP,
6
- LargeCommunity,
7
- LargeCommunityPattern,
8
- PolicyRule,
9
- Prefix,
10
- StandardCommunity,
11
- StandardCommunityPattern,
12
- } from "../schema";
13
-
14
- /** The prefix lengths a BGP import accepts. */
15
- export type SanityLengths = {
16
- /** Shortest and longest IPv4 prefix accepted. /8 to /24 when absent. */
17
- ipv4?: { min: number; max: number };
18
- /** Shortest and longest IPv6 prefix accepted. /16 to /48 when absent. */
19
- ipv6?: { min: number; max: number };
20
- };
21
-
22
- /** A prefix we announce, and what it is. */
23
- export type Announced = { prefix: Prefix; description?: string };
24
-
25
- /** What `Bgp.learn` builds an import from. */
26
- export type LearnOptions<P extends string> = {
27
- /** Rules of your own, first. */
28
- before?: readonly PolicyRule<P, never>[];
29
- /** A policy holding `Bgp.sanity()` to call. The sanity rules are written in the import itself when absent. */
30
- sanity?: P;
31
- /** The source address for routes of its family, one per family. */
32
- sources?: readonly IP[];
33
- /** Preference for the routes learned. Higher wins. */
34
- local_pref?: number;
35
- /** Where the route came from: `{ standard: "64500:100" }` or `{ large: "64500:1:1" }` adds that community, and a rule, such as `communityScheme(...).tag(...)`, is used as it is. */
36
- tag?: { standard: StandardCommunity } | { large: LargeCommunity } | PolicyRule<never, never>;
37
- };
38
-
39
- /** What `Bgp.defaultOnly` sets on the default routes it takes. */
40
- export type DefaultOnlyOptions = {
41
- /** Preference for the default route. Higher wins. */
42
- local_pref?: number;
43
- /** Shown beside each rule. `Default route, ipv4` and `Default route, ipv6` when absent. */
44
- description?: string;
45
- /** The source address for the default of its family, one per family. */
46
- sources?: readonly IP[];
47
- /** End with `{ description: "Nothing else", action: "reject" }`, so the policy takes the default route only. Off by default: any other route goes on to the rules after these. */
48
- reject?: boolean;
49
- };
50
-
51
- /** What `Bgp.announce` writes after the accepts. */
52
- export type AnnounceOptions = {
53
- /** End with `{ description: "Nothing else", action: "reject" }`, so nothing else leaves. On by default: it stops a route leak when the reject is forgotten. Turn it off for an export that adds rules after these. */
54
- reject?: boolean;
55
- };
56
-
57
- /** Kinds of neighbor and the number each is tagged with, such as `{ transit: 1, customer: 3 }`. */
58
- export type CommunityClasses = Readonly<Record<string, number>>;
59
-
60
- /**
61
- * A BGP community scheme of large communities, `asn:function:parameter`, in the style of RFC 8195. Each field names the function number your network uses for it; leave out what your network does not offer.
62
- *
63
- * The type parameters keep the exact numbers you write, so the communities `Bgp.communityScheme` gives back show them in the editor: Asn your AS, LearnedFrom the function of `learned_from`, Classes its classes.
64
- */
65
- export type CommunityScheme<
66
- Asn extends number = number,
67
- LearnedFrom extends number = number,
68
- Classes extends CommunityClasses = CommunityClasses,
69
- > = {
70
- /** Your AS. It is the first part of every community. */
71
- asn: Asn;
72
- /** Set on import: the kind of neighbor a route came from, `asn:function:class`. */
73
- learned_from?: { function: LearnedFrom; classes: Classes };
74
- /** Set on import: the site a route was learned at, `asn:function:site`. `sites` names the sites, for the catalogue. */
75
- learned_at?: { function: number; sites?: Readonly<Record<number, string>> };
76
- /** Set on import: the AS a route was learned from, `asn:function:asn`. */
77
- learned_from_as?: number;
78
- /** A neighbor asks: do not announce the route to an AS, `asn:function:asn`, or to anyone, `asn:function:0`. */
79
- do_not_announce?: number;
80
- /** A neighbor asks: prepend toward an AS once, twice or three times, `asn:function:asn`. */
81
- prepend?: { once?: number; twice?: number; three_times?: number };
82
- /**
83
- * A neighbor asks: drop all traffic to this route, `asn:function:0`, usually one address under attack. Your router drops it. An upstream whose own blackhole community you know (`communities.blackhole` on the neighbor you pass to `actions`) receives the route with it and drops the traffic too; any other never receives the route.
84
- */
85
- blackhole?: { function: number };
86
- };
87
-
88
- /** The communities another network offers us, as its NOC gives them: what it does with a route we send it. */
89
- export type NeighborCommunities = {
90
- /** It drops traffic to a route we send it with this community. */
91
- blackhole?: StandardCommunity | LargeCommunity;
92
- /** It does not pass a route we send it with this community to the AS given, such as an exchange's route servers' `rs-asn:0:peer-asn`. */
93
- do_not_announce_to?: (asn: number) => StandardCommunity | LargeCommunity;
94
- };
95
-
96
- /** A neighbor an export goes to: its AS, or its AS and the communities it offers. */
97
- export type ExportNeighbor = number | { asn: number; communities?: NeighborCommunities };
98
-
99
- /** The class names a scheme declares, or any name when it declares none. */
100
- type ClassName<Classes extends CommunityClasses> = {} extends Classes ? string : Extract<keyof Classes, string>;
101
-
102
- /** BGP policy: an import sanity policy, the shapes most imports and exports take, a community scheme, and helpers that read a device's BGP neighbors. */
103
- export class Bgp {
104
- private static readonly PREPENDS = [
105
- ["once", 1],
106
- ["twice", 2],
107
- ["three_times", 3],
108
- ] as const;
109
-
110
- /** The default route of each family. */
111
- private static readonly DEFAULTS = [
112
- { family: "ipv4", prefix: "0.0.0.0/0" },
113
- { family: "ipv6", prefix: "::/0" },
114
- ] as const;
115
-
116
- /** Route policy rules rejecting what no neighbor should send: prefixes too short or too long for the global table, and RPKI invalid. Place it first in an import, or as a policy other imports call. */
117
- static sanity(lengths: SanityLengths = {}): PolicyRule<never, never>[] {
118
- const ipv4 = lengths.ipv4 ?? { min: 8, max: 24 };
119
- const ipv6 = lengths.ipv6 ?? { min: 16, max: 48 };
120
-
121
- return [
122
- {
123
- description: `IPv4 shorter than /${ipv4.min}`,
124
- match: { family: "ipv4", prefix_length: { max: ipv4.min - 1 } },
125
- action: "reject",
126
- },
127
- {
128
- description: `IPv4 longer than /${ipv4.max}`,
129
- match: { family: "ipv4", prefix_length: { min: ipv4.max + 1 } },
130
- action: "reject",
131
- },
132
- {
133
- description: `IPv6 shorter than /${ipv6.min}`,
134
- match: { family: "ipv6", prefix_length: { max: ipv6.min - 1 } },
135
- action: "reject",
136
- },
137
- {
138
- description: `IPv6 longer than /${ipv6.max}`,
139
- match: { family: "ipv6", prefix_length: { min: ipv6.max + 1 } },
140
- action: "reject",
141
- },
142
- { description: "RPKI invalid", match: { rpki: "invalid" }, action: "reject" },
143
- ];
144
- }
145
-
146
- /**
147
- * An export that announces each prefix, at exactly its length. It returns, in order:
148
- *
149
- * 1. `{ description, match: { prefix }, action: "accept" }` for each prefix;
150
- * 2. `{ description: "Nothing else", action: "reject" }`, the unconditional reject `Checks.exportsEndInReject` looks for.
151
- *
152
- * The reject is there by default, so a forgotten one can't leak routes. `{ reject: false }` leaves it out, for an export that adds rules after these and ends in a reject of its own.
153
- */
154
- static announce(prefixes: readonly Announced[], options: AnnounceOptions = {}): PolicyRule<never, never>[] {
155
- return [
156
- ...prefixes.map(({ prefix, description }) => ({
157
- ...(description ? { description } : {}),
158
- match: { prefix },
159
- action: "accept" as const,
160
- })),
161
- ...(options.reject === false ? [] : [Bgp.NOTHING_ELSE]),
162
- ];
163
- }
164
-
165
- /** The reject that ends a policy: nothing else gets through. */
166
- private static readonly NOTHING_ELSE: PolicyRule<never, never> = { description: "Nothing else", action: "reject" };
167
-
168
- /**
169
- * An import policy that takes a neighbor's routes. It returns these rules, in order, each only when its option is given:
170
- *
171
- * 1. the `before` rules, as written;
172
- * 2. `{ call: sanity }`, or the rules of `Bgp.sanity()` written in when `sanity` is left out;
173
- * 3. `{ set: { preferred_source: source } }` for each of `sources`, one per family;
174
- * 4. `{ set: { local_pref } }`;
175
- * 5. the tag: `{ description: "Tag 64500:1:1", set: { add_large_communities: ["64500:1:1"] } }` for `{ large: "64500:1:1" }`, `add_communities` for `{ standard }`, or the rule given;
176
- * 6. `{ action: "accept" }`.
177
- *
178
- * So `Bgp.learn({ sanity: "SANITY", local_pref: 200, tag: { large: "64500:1:1" } })` is
179
- * `[{ call: "SANITY" }, { set: { local_pref: 200 } }, { description: "Tag 64500:1:1", set: { add_large_communities: ["64500:1:1"] } }, { action: "accept" }]`.
180
- *
181
- * FortiOS refuses it: a route map stops at the first rule that matches, so it can't hold rules 3 to 5, which change a route and go on; and its sanity rules match RPKI, which FortiOS doesn't have. `validate` names each such rule. On FortiOS, write the import by hand with what rules 3 to 5 set in the rule that accepts.
182
- */
183
- static learn<const P extends string = never>(options: LearnOptions<P> = {}): PolicyRule<P, never>[] {
184
- const { tag } = options;
185
-
186
- return [
187
- ...(options.before ?? []),
188
- ...(options.sanity ? [{ call: options.sanity }] : Bgp.sanity()),
189
- ...(options.sources ?? []).map((source) => ({ set: { preferred_source: source } })),
190
- ...(options.local_pref !== undefined ? [{ set: { local_pref: options.local_pref } }] : []),
191
- ...(tag === undefined ? [] : ["standard" in tag ? Bgp.adding(tag.standard) : "large" in tag ? Bgp.adding(tag.large) : tag]),
192
- { action: "accept" },
193
- ];
194
- }
195
-
196
- /**
197
- * Import rules that take the default route from a transit. They are, in order:
198
- *
199
- * 1. `{ description, match: { prefix: "0.0.0.0/0" }, set: { local_pref, preferred_source }, action: "accept" }`;
200
- * 2. the same for `::/0`;
201
- * 3. with `{ reject: true }` only, `{ description: "Nothing else", action: "reject" }`.
202
- *
203
- * Without `reject`, any other route goes on to the rules after these, so `[...Bgp.defaultOnly(), ...Bgp.learn()]` takes both. Each of `sources` is the source address for the default of its own family; `set` holds only what is given.
204
- */
205
- static defaultOnly(options: DefaultOnlyOptions = {}): PolicyRule<never, never>[] {
206
- const defaults = Bgp.DEFAULTS.map(({ family, prefix }) => {
207
- const source = options.sources?.find((address) => Address.family(address) === family);
208
- const set = {
209
- ...(options.local_pref !== undefined ? { local_pref: options.local_pref } : {}),
210
- ...(source ? { preferred_source: source } : {}),
211
- };
212
-
213
- return {
214
- description: options.description ?? `Default route, ${family}`,
215
- match: { prefix },
216
- ...(Object.keys(set).length ? { set } : {}),
217
- action: "accept" as const,
218
- };
219
- });
220
-
221
- return options.reject ? [...defaults, Bgp.NOTHING_ELSE] : defaults;
222
- }
223
-
224
- /** A rule adding one community, standard or large. */
225
- private static adding(community: StandardCommunity | LargeCommunity): PolicyRule<never, never> {
226
- const large = community.split(":").length === 3;
227
- return {
228
- description: `Tag ${community}`,
229
- set: large ? { add_large_communities: [community as LargeCommunity] } : { add_communities: [community as StandardCommunity] },
230
- };
231
- }
232
-
233
- /** Each BGP neighbor's own policies and role, or its group's. */
234
- static neighborPolicies(device: Device) {
235
- const bgp = device.routing?.bgp;
236
-
237
- return Object.entries(bgp?.neighbors ?? {}).map(([name, neighbor]) => {
238
- const group = neighbor.group ? bgp?.groups?.[neighbor.group] : undefined;
239
-
240
- return {
241
- name,
242
- address: neighbor.address,
243
- role: neighbor.role ?? group?.role,
244
- import: neighbor.import ?? group?.import,
245
- export: neighbor.export ?? group?.export,
246
- };
247
- });
248
- }
249
-
250
- /** The prefixes an export policy accepts by name. */
251
- static offered(device: Device, policy: string | undefined): Prefix[] {
252
- const rules = policy ? (device.policies?.[policy] ?? []) : [];
253
- return rules.filter((rule) => rule.action === "accept" && rule.match?.prefix).map((rule) => rule.match!.prefix!);
254
- }
255
-
256
- /**
257
- * Policy rules and a catalogue from one community scheme, so the communities your routers set, the ones they act on, and the ones you publish never disagree.
258
- *
259
- * - `tag(neighborClass, neighborAs, { site, trusted })`: one rule for an import policy, `{ description: "Learned from AS<n>", set }`. Its `set` removes your standard communities and your large ones (a `trusted` neighbor loses only the "where it came from" functions, so it keeps its requests: do not announce, prepend, blackhole), then adds `learned_from`, `learned_at` and `learned_from_as` where the scheme has them. `site` is needed when the scheme has `learned_at`.
260
- * Each export helper takes the neighbor: its AS, or `{ asn, communities }` with the communities it offers, such as an `Upstreams.catalog` entry.
261
- *
262
- * - `actions(neighbor)`: the rules for an export policy toward one neighbor, before the rules that accept. They are `[...blackholeExport(n), ...rejectDoNotAnnounce(n), ...prepends(n), stripOurs()]`; toward a neighbor that takes blackholes (`communities.blackhole`), `rejectDoNotAnnounce(n)` comes first, so a route asked not to go there is never passed on as a blackhole.
263
- * - `blackholeExport(neighbor)`: a blackholed route goes no further, `Blackholed here, never announced`, unless the neighbor has `communities.blackhole`: then it is passed on with it, so the neighbor drops the traffic too. Nothing without `blackhole` in the scheme.
264
- * - `rejectDoNotAnnounce(neighbor)`: rejects a route asked not to be announced anywhere, then one asked not to be announced to this neighbor. Nothing without `do_not_announce`.
265
- * - `prepends(neighbor)`: prepends once, twice or three times toward this neighbor, where a route asks. Nothing without `prepend`.
266
- * - `stripOurs()`: one rule removing every community of yours, `Our communities stay inside our network`.
267
- * - `blackhole(prefixSets)`: rules for the import policy of a neighbor you trust with blackholes, before anything that rejects long prefixes. They take a route the neighbor asked to blackhole, but only inside `prefixSets`, its own space, and drop traffic to it here. One rule per set, so one call covers both families.
268
- * - `catalogue`: every community, for `communities` in `defineNetwork`.
269
- * - `community(fn, parameter)`: one community of yours, for a rule of your own.
270
- * - `learned_from`: the community each class is tagged with, by class name, such as `learned_from.customer`.
271
- */
272
- static communityScheme<
273
- const Asn extends number,
274
- const LearnedFrom extends number = number,
275
- const Classes extends CommunityClasses = {},
276
- >(scheme: CommunityScheme<Asn, LearnedFrom, Classes>) {
277
- type Class = ClassName<Classes>;
278
-
279
- const { asn } = scheme;
280
-
281
- /** One community of yours, `asn:fn:parameter`. */
282
- const community = <const Fn extends number, const Parameter extends number>(fn: Fn, parameter: Parameter) =>
283
- `${asn}:${fn}:${parameter}` as `${Asn}:${Fn}:${Parameter}`;
284
-
285
- /** Every value of one of your functions, `asn:fn:*`, for a list of communities to remove. */
286
- const anyOf = (fn: number): LargeCommunityPattern => `${asn}:${fn}:*`;
287
-
288
- /** A line of the catalogue, where `nnn` stands for any number. */
289
- const written = (fn: number, parameter: number | string) => `${asn}:${fn}:${parameter}`;
290
-
291
- const ours = {
292
- standard: [`${asn}:*`] as StandardCommunityPattern[],
293
- large: [`${asn}:*:*`] as LargeCommunityPattern[],
294
- };
295
-
296
- const learnedFrom = Object.fromEntries(
297
- Object.entries<number>(scheme.learned_from?.classes ?? {}).map(([name, value]) => [
298
- name,
299
- community(scheme.learned_from!.function, value),
300
- ])
301
- ) as { readonly [Name in keyof Classes]: `${Asn}:${LearnedFrom}:${Classes[Name]}` };
302
-
303
- const information = [scheme.learned_from?.function, scheme.learned_at?.function, scheme.learned_from_as].filter(
304
- (fn): fn is number => fn !== undefined
305
- );
306
-
307
- const tag = (
308
- neighborClass: Class,
309
- neighborAs: number,
310
- options: { site?: number; trusted?: boolean } = {}
311
- ): PolicyRule<never, never> => {
312
- const { site, trusted = false } = options;
313
- if (scheme.learned_at && site === undefined)
314
- throw new Error(`Bgp.communityScheme: tag for AS${neighborAs} needs a site, for learned_at`);
315
-
316
- return {
317
- description: `Learned from AS${neighborAs}`,
318
- set: {
319
- remove_communities: ours.standard,
320
- // A trusted neighbor keeps its requests and loses only the "where it came from" tags, which are ours to set. Anyone else loses every community of ours.
321
- remove_large_communities: trusted ? information.map(anyOf) : ours.large,
322
- add_large_communities: [
323
- ...(scheme.learned_from
324
- ? [community(scheme.learned_from.function, scheme.learned_from.classes[neighborClass])]
325
- : []),
326
- ...(scheme.learned_at ? [community(scheme.learned_at.function, site!)] : []),
327
- ...(scheme.learned_from_as !== undefined ? [community(scheme.learned_from_as, neighborAs)] : []),
328
- ],
329
- },
330
- };
331
- };
332
-
333
- const blackhole = <S extends string>(prefixSets: readonly S[]): PolicyRule<never, S>[] => {
334
- const fn = scheme.blackhole?.function;
335
- if (fn === undefined) throw new Error("Bgp.communityScheme: blackhole needs a function number in the scheme");
336
-
337
- return prefixSets.map((prefixSet) => ({
338
- description: "Blackhole, asked by the neighbor",
339
- match: { large_community: community(fn, 0), prefix_set: prefixSet },
340
- set: { blackhole: true },
341
- action: "accept",
342
- }));
343
- };
344
-
345
- /** The neighbor's AS. */
346
- const asnOf = (neighbor: ExportNeighbor) => (typeof neighbor === "number" ? neighbor : neighbor.asn);
347
-
348
- /** The neighbor's own blackhole community, when it takes blackholes from us. */
349
- const passOnTo = (neighbor: ExportNeighbor) => (typeof neighbor === "number" ? undefined : neighbor.communities?.blackhole);
350
-
351
- const blackholeExport = (neighbor: ExportNeighbor): PolicyRule<never, never>[] => {
352
- if (scheme.blackhole === undefined) return [];
353
-
354
- const passOn = passOnTo(neighbor);
355
- const match = { large_community: community(scheme.blackhole.function, 0) };
356
- if (!passOn) return [{ description: "Blackholed here, never announced", match, action: "reject" }];
357
-
358
- const large = passOn.split(":").length === 3;
359
- return [
360
- {
361
- description: `Blackhole passed on to AS${asnOf(neighbor)}`,
362
- match,
363
- set: {
364
- remove_communities: ours.standard,
365
- remove_large_communities: ours.large,
366
- ...(large
367
- ? { add_large_communities: [passOn as LargeCommunity] }
368
- : { add_communities: [passOn as StandardCommunity] }),
369
- },
370
- action: "accept",
371
- },
372
- ];
373
- };
374
-
375
- const rejectDoNotAnnounce = (neighbor: ExportNeighbor): PolicyRule<never, never>[] => {
376
- const neighborAs = asnOf(neighbor);
377
- const fn = scheme.do_not_announce;
378
- if (fn === undefined) return [];
379
-
380
- return [
381
- { description: "Asked not to be announced anywhere", match: { large_community: community(fn, 0) }, action: "reject" },
382
- {
383
- description: `Asked not to be announced to AS${neighborAs}`,
384
- match: { large_community: community(fn, neighborAs) },
385
- action: "reject",
386
- },
387
- ];
388
- };
389
-
390
- const prepends = (neighbor: ExportNeighbor): PolicyRule<never, never>[] =>
391
- Bgp.PREPENDS.flatMap(([name, times]) => {
392
- const neighborAs = asnOf(neighbor);
393
- const fn = scheme.prepend?.[name];
394
- if (fn === undefined) return [];
395
-
396
- return [
397
- {
398
- description: `Asked to prepend ${name.replace("_", " ")} to AS${neighborAs}`,
399
- match: { large_community: community(fn, neighborAs) },
400
- set: { prepend: times },
401
- },
402
- ];
403
- });
404
-
405
- const stripOurs = (): PolicyRule<never, never> => ({
406
- description: "Our communities stay inside our network",
407
- set: { remove_communities: ours.standard, remove_large_communities: ours.large },
408
- });
409
-
410
- // A route asked not to be announced to a neighbor is never passed on to it as a blackhole either; where nothing is passed on, both only reject.
411
- const actions = (neighbor: ExportNeighbor): PolicyRule<never, never>[] =>
412
- passOnTo(neighbor)
413
- ? [...rejectDoNotAnnounce(neighbor), ...blackholeExport(neighbor), ...prepends(neighbor), stripOurs()]
414
- : [...blackholeExport(neighbor), ...rejectDoNotAnnounce(neighbor), ...prepends(neighbor), stripOurs()];
415
-
416
- const catalogue: Community[] = [
417
- ...Object.entries<number>(scheme.learned_from?.classes ?? {}).map(([name, value]) => ({
418
- community: written(scheme.learned_from!.function, value),
419
- description: `Learned from ${Bgp.article(name)}`,
420
- })),
421
- ...(scheme.learned_at
422
- ? [
423
- ...Object.entries(scheme.learned_at.sites ?? {}).map(([site, name]) => ({
424
- community: written(scheme.learned_at!.function, site),
425
- description: `Learned in ${name}`,
426
- })),
427
- { community: written(scheme.learned_at.function, "nnn"), description: "Learned at site $0" },
428
- ]
429
- : []),
430
- ...(scheme.learned_from_as !== undefined
431
- ? [{ community: written(scheme.learned_from_as, "nnn"), description: "Learned from AS$0" }]
432
- : []),
433
- ...(scheme.do_not_announce !== undefined
434
- ? [
435
- { community: written(scheme.do_not_announce, 0), description: "Do not announce to anyone" },
436
- { community: written(scheme.do_not_announce, "nnn"), description: "Do not announce to AS$0" },
437
- ]
438
- : []),
439
- ...Bgp.PREPENDS.flatMap(([name]) => {
440
- const fn = scheme.prepend?.[name];
441
- return fn === undefined
442
- ? []
443
- : [{ community: written(fn, "nnn"), description: `Prepend ${name.replace("_", " ")} to AS$0` }];
444
- }),
445
- ...(scheme.blackhole !== undefined
446
- ? [
447
- {
448
- community: written(scheme.blackhole.function, 0),
449
- description: `Blackhole: dropped in AS${asn}, and by the upstreams that take it`,
450
- },
451
- ]
452
- : []),
453
- ];
454
-
455
- return {
456
- community,
457
- learned_from: learnedFrom,
458
- tag,
459
- actions,
460
- blackholeExport,
461
- rejectDoNotAnnounce,
462
- prepends,
463
- stripOurs,
464
- blackhole,
465
- catalogue,
466
- };
467
- }
468
-
469
- /** A word with its article: "a customer", "an upstream". */
470
- private static article(word: string): string {
471
- return /^[aeiou]/i.test(word) ? `an ${word}` : `a ${word}`;
472
- }
473
- }
@@ -1,74 +0,0 @@
1
- import { Catalog } from "../platforms/catalog";
2
-
3
- /** The far end of a port, for a description of your own: the device, its port by Circuit's name, and the port as printed on the device when that end names its platform and model. */
4
- export type FarEnd = { device: string; port: string; label?: string };
5
-
6
- /** One end of a cable entry: the device, and its platform and model if a description should name its ports as printed on it. */
7
- type End = { device: string; platform?: string; model?: string };
8
-
9
- /** What every cable of one entry shares. */
10
- type Shared = {
11
- /** What is between the two ends, such as a carrier's local loop. Both ends get it on their `link`. */
12
- through?: string;
13
- /** A port's description from its far end, such as `(far) => \`${far.device} ${far.label}\``. No description when absent. */
14
- describe?: (far: FarEnd) => string;
15
- };
16
-
17
- /** A port a cable builds: the other end, the LAG it is in, and a description when `describe` gives one. */
18
- type CablePort<L extends string> = {
19
- link: { device: string; port: string; through?: string };
20
- lag?: L;
21
- description?: string;
22
- };
23
-
24
- /** Ports for both ends of the cables between two of our devices, each cable written once. */
25
- export class Cables {
26
- /**
27
- * One cable, both ends. Spread each end into its device's `ports`:
28
- *
29
- * ```ts
30
- * const uplink = Cables.link({ a: { device: "router-1", port: "25g-1" }, b: { device: "switch-1", port: "10g-48" } });
31
- * // router-1: ports: { ...uplink.a }
32
- * ```
33
- */
34
- static link<const NA extends string, const NB extends string>(spec: Shared & { a: End & { port: NA }; b: End & { port: NB } }) {
35
- return {
36
- a: { [spec.a.port]: Cables.end(spec, spec.b, spec.b.port) } as Record<NA, CablePort<never>>,
37
- b: { [spec.b.port]: Cables.end(spec, spec.a, spec.a.port) } as Record<NB, CablePort<never>>,
38
- };
39
- }
40
-
41
- /**
42
- * The cables of a LAG, both ends, from one list read from end A: `{ "25g-1": "10g-2" }` is a cable from A's 25g-1 to B's 10g-2. Each port is a member of its end's LAG. Spread each end into its device's `ports`; the LAG itself stays on each device.
43
- */
44
- static lag<const LA extends string, const LB extends string, const C extends Readonly<Record<string, string>>>(
45
- spec: Shared & { a: End & { lag: LA }; b: End & { lag: LB }; cables: C }
46
- ) {
47
- const pairs = Object.entries(spec.cables);
48
-
49
- const taken = new Map<string, string>();
50
- for (const [near, far] of pairs) {
51
- const other = taken.get(far);
52
- if (other) throw new Error(`Cables.lag: ${spec.b.device} ${far} is cabled to both ${other} and ${near} on ${spec.a.device}`);
53
- taken.set(far, near);
54
- }
55
-
56
- const a = Object.fromEntries(pairs.map(([near, far]) => [near, { ...Cables.end(spec, spec.b, far), lag: spec.a.lag }]));
57
- const b = Object.fromEntries(pairs.map(([near, far]) => [far, { ...Cables.end(spec, spec.a, near), lag: spec.b.lag }]));
58
-
59
- return {
60
- a: a as Record<keyof C & string, CablePort<LA>>,
61
- b: b as Record<C[keyof C], CablePort<LB>>,
62
- };
63
- }
64
-
65
- /** A port facing `port` on the far end. */
66
- private static end(shared: Shared, far: End, port: string): CablePort<never> {
67
- const label = far.platform && far.model ? Catalog.spell(far.platform, far.model, port) : undefined;
68
-
69
- return {
70
- link: { device: far.device, port, ...(shared.through ? { through: shared.through } : {}) },
71
- ...(shared.describe ? { description: shared.describe({ device: far.device, port, ...(label ? { label } : {}) }) } : {}),
72
- };
73
- }
74
- }