@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.
Files changed (240) hide show
  1. package/README.md +14 -15
  2. package/dist/chunks/index-20kcjxjg.js +174 -0
  3. package/dist/chunks/index-2r4bbhac.js +977 -0
  4. package/dist/chunks/index-n7w3tg5y.js +149 -0
  5. package/dist/chunks/new-w3zys5y4.js +172 -0
  6. package/dist/chunks/server-gzsf5me5.js +259 -0
  7. package/dist/cli/index.js +11345 -9024
  8. package/dist/helpers/index.js +594 -0
  9. package/dist/index.js +9 -28
  10. package/dist/types/core/{addresses.d.ts → address-space.d.ts} +23 -7
  11. package/dist/types/core/address.d.ts +44 -0
  12. package/dist/types/core/cabling.d.ts +41 -0
  13. package/dist/types/core/cross-checks.d.ts +28 -0
  14. package/dist/types/core/define.d.ts +25 -3
  15. package/dist/types/core/project-files.d.ts +12 -0
  16. package/dist/types/core/schema-docs.d.ts +13 -4
  17. package/dist/types/core/secrets.d.ts +62 -21
  18. package/dist/types/core/{graph.d.ts → topology.d.ts} +32 -12
  19. package/dist/types/core/utils.d.ts +8 -0
  20. package/dist/types/core/validator.d.ts +19 -0
  21. package/dist/types/helpers/bgp.d.ts +97 -0
  22. package/dist/types/helpers/checks.d.ts +32 -0
  23. package/dist/types/helpers/exchange.d.ts +49 -0
  24. package/dist/types/helpers/filters.d.ts +51 -0
  25. package/dist/types/helpers/index.d.ts +10 -0
  26. package/dist/types/helpers/route-server.d.ts +52 -0
  27. package/dist/types/index.d.ts +5 -3
  28. package/dist/types/integrations/librenms.d.ts +60 -0
  29. package/dist/types/integrations/netbox.d.ts +89 -0
  30. package/dist/types/integrations/peeringdb.d.ts +24 -0
  31. package/dist/types/integrations/prometheus.d.ts +24 -0
  32. package/dist/types/integrations/registries.d.ts +8 -0
  33. package/dist/types/platforms/bgp-wait.d.ts +18 -0
  34. package/dist/types/platforms/catalog.d.ts +109 -0
  35. package/dist/types/platforms/derive.d.ts +17 -0
  36. package/dist/types/platforms/fortios/firewall.d.ts +45 -0
  37. package/dist/types/platforms/fortios/index.d.ts +134 -0
  38. package/dist/types/platforms/fortios/names.d.ts +52 -0
  39. package/dist/types/platforms/fortios/plan.d.ts +104 -0
  40. package/dist/types/platforms/fortios/render.d.ts +75 -0
  41. package/dist/types/platforms/fortios/routing.d.ts +56 -0
  42. package/dist/types/platforms/fortios/tree.d.ts +37 -0
  43. package/dist/types/platforms/index.d.ts +13 -0
  44. package/dist/types/platforms/junos/bgp.d.ts +44 -0
  45. package/dist/types/platforms/junos/index.d.ts +114 -0
  46. package/dist/types/platforms/junos/render.d.ts +154 -0
  47. package/dist/types/platforms/line-diff.d.ts +67 -0
  48. package/dist/types/platforms/platform.d.ts +120 -0
  49. package/dist/types/platforms/raisecom-ros/index.d.ts +71 -0
  50. package/dist/types/platforms/raisecom-ros/render.d.ts +41 -0
  51. package/dist/types/platforms/routeros/export.d.ts +59 -0
  52. package/dist/types/platforms/routeros/index.d.ts +96 -0
  53. package/dist/types/{adapters/devices → platforms}/routeros/models.d.ts +4 -0
  54. package/dist/types/platforms/routeros/plan.d.ts +54 -0
  55. package/dist/types/platforms/routeros/render.d.ts +240 -0
  56. package/dist/types/platforms/vrp/index.d.ts +83 -0
  57. package/dist/types/platforms/vrp/render.d.ts +96 -0
  58. package/dist/types/schema/common.d.ts +12 -6
  59. package/dist/types/schema/index.d.ts +1 -0
  60. package/dist/types/schema/interfaces.d.ts +14 -6
  61. package/dist/types/schema/policy.d.ts +18 -10
  62. package/dist/types/schema/routing.d.ts +14 -1
  63. package/dist/types/schema/system.d.ts +16 -6
  64. package/dist/types/schema/time-zones.d.ts +2 -0
  65. package/dist/types/transport/console.d.ts +79 -23
  66. package/dist/types/transport/path-check.d.ts +32 -0
  67. package/dist/types/transport/ssh.d.ts +17 -15
  68. package/package.json +18 -12
  69. package/src/cli/graph-format.ts +91 -0
  70. package/src/cli/index.ts +1074 -484
  71. package/src/cli/new.ts +208 -175
  72. package/src/cli/rename.ts +97 -91
  73. package/src/core/address-space.ts +320 -0
  74. package/src/core/address.ts +202 -0
  75. package/src/core/cabling.ts +154 -0
  76. package/src/core/cross-checks.ts +364 -0
  77. package/src/core/define.ts +26 -3
  78. package/src/core/project-files.ts +35 -0
  79. package/src/core/schema-docs.ts +138 -130
  80. package/src/core/secrets.ts +170 -76
  81. package/src/core/topology.ts +656 -0
  82. package/src/core/utils.ts +37 -0
  83. package/src/core/validator.ts +407 -0
  84. package/src/helpers/bgp.ts +301 -0
  85. package/src/helpers/checks.ts +178 -0
  86. package/src/helpers/exchange.ts +117 -0
  87. package/src/helpers/filters.ts +107 -0
  88. package/src/helpers/index.ts +12 -0
  89. package/src/helpers/route-server.ts +212 -0
  90. package/src/index.ts +6 -3
  91. package/src/integrations/librenms.ts +291 -0
  92. package/src/integrations/netbox.ts +620 -0
  93. package/src/integrations/peeringdb.ts +173 -0
  94. package/src/integrations/prometheus.ts +90 -0
  95. package/src/integrations/registries.ts +25 -0
  96. package/src/platforms/bgp-wait.ts +41 -0
  97. package/src/platforms/catalog.ts +69 -0
  98. package/src/platforms/derive.ts +117 -0
  99. package/src/{adapters/devices → platforms}/fortios/TRAPS.md +6 -0
  100. package/src/platforms/fortios/firewall.ts +377 -0
  101. package/src/platforms/fortios/index.ts +1060 -0
  102. package/src/platforms/fortios/names.ts +130 -0
  103. package/src/platforms/fortios/plan.ts +929 -0
  104. package/src/platforms/fortios/render.ts +779 -0
  105. package/src/platforms/fortios/routing.ts +476 -0
  106. package/src/platforms/fortios/tree.ts +129 -0
  107. package/src/platforms/index.ts +36 -0
  108. package/src/{adapters/devices → platforms}/junos/TRAPS.md +8 -0
  109. package/src/platforms/junos/bgp.ts +293 -0
  110. package/src/platforms/junos/index.ts +1006 -0
  111. package/src/platforms/junos/render.ts +967 -0
  112. package/src/platforms/line-diff.ts +244 -0
  113. package/src/platforms/platform.ts +122 -0
  114. package/src/platforms/raisecom-ros/TRAPS.md +40 -0
  115. package/src/platforms/raisecom-ros/index.ts +731 -0
  116. package/src/platforms/raisecom-ros/render.ts +328 -0
  117. package/src/{adapters/devices → platforms}/routeros/TRAPS.md +16 -2
  118. package/src/platforms/routeros/export.ts +342 -0
  119. package/src/platforms/routeros/index.ts +636 -0
  120. package/src/{adapters/devices → platforms}/routeros/models.ts +2 -0
  121. package/src/platforms/routeros/plan.ts +444 -0
  122. package/src/platforms/routeros/render.ts +1483 -0
  123. package/src/platforms/vrp/TRAPS.md +45 -0
  124. package/src/platforms/vrp/index.ts +652 -0
  125. package/src/platforms/vrp/render.ts +615 -0
  126. package/src/schema/common.ts +15 -6
  127. package/src/schema/index.ts +1 -0
  128. package/src/schema/interfaces.ts +15 -5
  129. package/src/schema/policy.ts +22 -10
  130. package/src/schema/routing.ts +16 -1
  131. package/src/schema/system.ts +16 -6
  132. package/src/schema/time-zones.ts +482 -0
  133. package/src/studio/data.ts +172 -168
  134. package/src/studio/git.ts +95 -90
  135. package/src/studio/public/app.js +161 -150
  136. package/src/studio/public/compare.js +219 -212
  137. package/src/studio/public/diff.js +77 -75
  138. package/src/studio/public/dom.js +118 -108
  139. package/src/studio/public/evaluate.js +584 -545
  140. package/src/studio/public/export.js +120 -118
  141. package/src/studio/public/graph.js +1343 -1266
  142. package/src/studio/public/net.js +115 -109
  143. package/src/studio/public/tables.js +1135 -1083
  144. package/src/studio/public/tools.js +561 -529
  145. package/src/studio/server.ts +202 -196
  146. package/src/transport/console.ts +363 -271
  147. package/src/transport/path-check.ts +91 -0
  148. package/src/transport/ssh.ts +98 -94
  149. package/templates/colocation/checks.ts +11 -6
  150. package/templates/colocation/edge-01.ts +9 -9
  151. package/templates/colocation/routing.ts +2 -2
  152. package/templates/colocation/tenancy.ts +3 -3
  153. package/templates/colocation/tor-01.ts +3 -3
  154. package/templates/edge-router/checks.ts +4 -4
  155. package/templates/edge-router/edge-01.ts +5 -5
  156. package/templates/edge-router/routing.ts +2 -2
  157. package/templates/project/AGENTS.md +5 -1
  158. package/templates/single-site/checks.ts +2 -2
  159. package/dist/chunks/index-47debt16.js +0 -956
  160. package/dist/chunks/index-v8zgr7wm.js +0 -128
  161. package/dist/chunks/new-ny89s7y7.js +0 -166
  162. package/dist/chunks/server-tjbb3rqc.js +0 -251
  163. package/dist/presets/index.js +0 -394
  164. package/dist/types/adapters/devices/catalog.d.ts +0 -86
  165. package/dist/types/adapters/devices/derive.d.ts +0 -7
  166. package/dist/types/adapters/devices/fortios/firewall.d.ts +0 -23
  167. package/dist/types/adapters/devices/fortios/index.d.ts +0 -58
  168. package/dist/types/adapters/devices/fortios/names.d.ts +0 -48
  169. package/dist/types/adapters/devices/fortios/plan.d.ts +0 -28
  170. package/dist/types/adapters/devices/fortios/render.d.ts +0 -38
  171. package/dist/types/adapters/devices/fortios/routing.d.ts +0 -32
  172. package/dist/types/adapters/devices/fortios/tree.d.ts +0 -33
  173. package/dist/types/adapters/devices/index.d.ts +0 -3
  174. package/dist/types/adapters/devices/junos/bgp.d.ts +0 -11
  175. package/dist/types/adapters/devices/junos/index.d.ts +0 -42
  176. package/dist/types/adapters/devices/junos/render.d.ts +0 -52
  177. package/dist/types/adapters/devices/lines.d.ts +0 -56
  178. package/dist/types/adapters/devices/raisecom-ros/index.d.ts +0 -28
  179. package/dist/types/adapters/devices/routeros/export.d.ts +0 -39
  180. package/dist/types/adapters/devices/routeros/index.d.ts +0 -30
  181. package/dist/types/adapters/devices/routeros/plan.d.ts +0 -4
  182. package/dist/types/adapters/devices/routeros/render.d.ts +0 -100
  183. package/dist/types/adapters/devices/types.d.ts +0 -40
  184. package/dist/types/adapters/devices/vrp/index.d.ts +0 -27
  185. package/dist/types/adapters/registries/index.d.ts +0 -3
  186. package/dist/types/adapters/registries/librenms.d.ts +0 -15
  187. package/dist/types/adapters/registries/peeringdb.d.ts +0 -16
  188. package/dist/types/adapters/registries/prometheus.d.ts +0 -16
  189. package/dist/types/core/across.d.ts +0 -4
  190. package/dist/types/core/addr.d.ts +0 -34
  191. package/dist/types/core/files.d.ts +0 -6
  192. package/dist/types/core/merge.d.ts +0 -4
  193. package/dist/types/core/time.d.ts +0 -2
  194. package/dist/types/core/validate.d.ts +0 -2
  195. package/dist/types/presets/index.d.ts +0 -183
  196. package/src/adapters/devices/catalog.ts +0 -41
  197. package/src/adapters/devices/derive.ts +0 -59
  198. package/src/adapters/devices/fortios/firewall.ts +0 -362
  199. package/src/adapters/devices/fortios/index.ts +0 -923
  200. package/src/adapters/devices/fortios/names.ts +0 -115
  201. package/src/adapters/devices/fortios/plan.ts +0 -888
  202. package/src/adapters/devices/fortios/render.ts +0 -768
  203. package/src/adapters/devices/fortios/routing.ts +0 -444
  204. package/src/adapters/devices/fortios/tree.ts +0 -121
  205. package/src/adapters/devices/index.ts +0 -9
  206. package/src/adapters/devices/junos/bgp.ts +0 -269
  207. package/src/adapters/devices/junos/index.ts +0 -862
  208. package/src/adapters/devices/junos/render.ts +0 -872
  209. package/src/adapters/devices/lines.ts +0 -212
  210. package/src/adapters/devices/raisecom-ros/TRAPS.md +0 -26
  211. package/src/adapters/devices/raisecom-ros/index.ts +0 -700
  212. package/src/adapters/devices/routeros/export.ts +0 -321
  213. package/src/adapters/devices/routeros/index.ts +0 -346
  214. package/src/adapters/devices/routeros/plan.ts +0 -398
  215. package/src/adapters/devices/routeros/render.ts +0 -1490
  216. package/src/adapters/devices/types.ts +0 -38
  217. package/src/adapters/devices/vrp/TRAPS.md +0 -26
  218. package/src/adapters/devices/vrp/index.ts +0 -865
  219. package/src/adapters/registries/index.ts +0 -19
  220. package/src/adapters/registries/librenms.ts +0 -92
  221. package/src/adapters/registries/peeringdb.ts +0 -169
  222. package/src/adapters/registries/prometheus.ts +0 -71
  223. package/src/cli/graph.ts +0 -89
  224. package/src/core/across.ts +0 -350
  225. package/src/core/addr.ts +0 -174
  226. package/src/core/addresses.ts +0 -316
  227. package/src/core/files.ts +0 -32
  228. package/src/core/graph.ts +0 -640
  229. package/src/core/merge.ts +0 -19
  230. package/src/core/time.ts +0 -15
  231. package/src/core/validate.ts +0 -348
  232. package/src/presets/index.ts +0 -579
  233. /package/dist/types/{adapters/devices → platforms}/fortios/models.d.ts +0 -0
  234. /package/dist/types/{adapters/devices → platforms}/junos/models.d.ts +0 -0
  235. /package/dist/types/{adapters/devices → platforms}/raisecom-ros/models.d.ts +0 -0
  236. /package/dist/types/{adapters/devices → platforms}/vrp/models.d.ts +0 -0
  237. /package/src/{adapters/devices → platforms}/fortios/models.ts +0 -0
  238. /package/src/{adapters/devices → platforms}/junos/models.ts +0 -0
  239. /package/src/{adapters/devices → platforms}/raisecom-ros/models.ts +0 -0
  240. /package/src/{adapters/devices → platforms}/vrp/models.ts +0 -0
@@ -0,0 +1,293 @@
1
+ // BGP, route policies, prefix sets and route origin validation, as Junos `set` statements.
2
+
3
+ import { Address } from "../../core/address";
4
+ import { Secrets } from "../../core/secrets";
5
+ import type { Device, Family, Neighbor, NeighborSettings, PolicyRule } from "../../schema";
6
+ import { JunosRender, type JunosStatements } from "./render";
7
+
8
+ type AnyRule = PolicyRule<string, string>;
9
+ type AnyNeighbor = Neighbor<string, string>;
10
+
11
+ /** BGP sessions, the policies they use, prefix sets and RPKI servers, added to one device's statements. */
12
+ export class JunosBgp {
13
+ /** The policy a session uses when it states none, so that it learns or announces nothing. */
14
+ private static readonly REJECT_ALL = "circuit-reject-all";
15
+
16
+ /** The policy every export starts with. BGP's own routes and the prefixes the device originates go on to the session's policy; the device's other routes never reach it. */
17
+ private static readonly ORIGIN = "circuit-bgp-origin";
18
+
19
+ /** The validator group every RPKI server joins. */
20
+ private static readonly RPKI_GROUP = "circuit";
21
+
22
+ /** A comment in the render naming a path whose statements `refresh` fills, so the plan keeps what the device holds there. */
23
+ static readonly GENERATED = "# generated ";
24
+
25
+ private static readonly FAMILY = { ipv4: "inet", ipv6: "inet6" } as const;
26
+
27
+ private static readonly VALIDATION = { valid: "valid", invalid: "invalid", "not-found": "unknown" } as const;
28
+
29
+ private constructor(
30
+ private readonly out: JunosStatements,
31
+ private readonly device: Device
32
+ ) {}
33
+
34
+ /** Add the device's prefix sets, RPKI servers, BGP sessions and the policies they use. */
35
+ static render(out: JunosStatements, device: Device): void {
36
+ const bgp = new JunosBgp(out, device);
37
+
38
+ bgp.prefixSets();
39
+ bgp.rpki();
40
+ bgp.sessions();
41
+ }
42
+
43
+ /** A neighbor's settings with its group's beneath them. */
44
+ static settingsOf(device: Device, neighbor: AnyNeighbor): NeighborSettings<string> {
45
+ const group = neighbor.group ? device.routing?.bgp?.groups?.[neighbor.group] : undefined;
46
+ return { ...group, ...neighbor };
47
+ }
48
+
49
+ /** A name made of a value's words, for a community or a path-length list: `64500:*` becomes `64500-any`. */
50
+ private static slug(value: string): string {
51
+ return value.replace(/\*/g, "any").replace(/[^A-Za-z0-9]+/g, "-");
52
+ }
53
+
54
+ /** A community as the platform writes a member: a large community carries the `large:` prefix. */
55
+ private static member(community: string, large: boolean): string {
56
+ return large ? `large:${community}` : community;
57
+ }
58
+
59
+ /** A term's name: its place, then the rule's description so a reader sees it on the device. */
60
+ private static termName(label: string, rule: AnyRule): string {
61
+ return JunosRender.quote(rule.description ? `${label} ${rule.description}` : label);
62
+ }
63
+
64
+ private sessions(): void {
65
+ const bgp = this.device.routing?.bgp;
66
+ if (!bgp) return;
67
+
68
+ this.out.set("routing-options autonomous-system", bgp.asn);
69
+
70
+ const used = new Set<string>();
71
+ let rejectAll = false;
72
+ let exports = false;
73
+
74
+ // One group per session: a group holds one type, internal or external, and the config's groups are only shared settings.
75
+ for (const [name, neighbor] of Object.entries(bgp.neighbors ?? {})) {
76
+ const settings = JunosBgp.settingsOf(this.device, neighbor);
77
+ const group = `protocols bgp group ${name}`;
78
+
79
+ this.out.set(group, "type", settings.remote_as === bgp.asn ? "internal" : "external");
80
+ this.out.set(group, "peer-as", settings.remote_as!);
81
+ if (settings.description) this.out.set(group, "description", JunosRender.quote(settings.description));
82
+ if (settings.local_address) this.out.set(group, "local-address", settings.local_address);
83
+ if (settings.multihop) this.out.set(group, "multihop");
84
+ if (settings.passive) this.out.set(group, "passive");
85
+ if (settings.hold_time !== undefined) this.out.set(group, "hold-time", settings.hold_time);
86
+ if (settings.password) this.out.set(group, "authentication-key", Secrets.marker(settings.password));
87
+ if (settings.bfd) JunosRender.bfd(this.out, group, this.device);
88
+
89
+ this.out.set(group, "import", settings.import ?? JunosBgp.REJECT_ALL);
90
+
91
+ if (settings.export) {
92
+ exports = true;
93
+ this.out.set(group, "export", JunosBgp.ORIGIN);
94
+ this.out.set(group, "export", settings.export);
95
+ } else this.out.set(group, "export", JunosBgp.REJECT_ALL);
96
+
97
+ for (const policy of [settings.import, settings.export]) {
98
+ if (policy) used.add(policy);
99
+ else rejectAll = true;
100
+ }
101
+
102
+ for (const family of settings.families ?? [Address.family(neighbor.address)]) {
103
+ const nlri = `${group} family ${JunosBgp.FAMILY[family]} unicast`;
104
+ this.out.set(nlri);
105
+
106
+ // A session over its limit stays down until it is cleared by hand.
107
+ if (settings.max_prefixes !== undefined) {
108
+ this.out.set(nlri, "prefix-limit maximum", settings.max_prefixes);
109
+ this.out.set(nlri, "prefix-limit teardown idle-timeout forever");
110
+ }
111
+ }
112
+
113
+ this.out.set(group, "neighbor", neighbor.address);
114
+ }
115
+
116
+ if (rejectAll) this.out.set(`policy-options policy-statement ${JunosBgp.REJECT_ALL} then reject`);
117
+
118
+ if (exports) this.origin();
119
+
120
+ for (const policy of [...used].sort()) this.policyStatement(policy);
121
+ }
122
+
123
+ /** BGP's routes and the originated prefixes go on to the session's policy; anything else is rejected. */
124
+ private origin(): void {
125
+ const statement = `policy-options policy-statement ${JunosBgp.ORIGIN}`;
126
+ this.out.set(statement, "term bgp from protocol bgp");
127
+ this.out.set(statement, "term bgp then next policy");
128
+
129
+ const networks = this.device.routing?.bgp?.networks ?? [];
130
+ for (const prefix of networks) this.out.set(statement, "term networks from route-filter", prefix, "exact");
131
+ if (networks.length) this.out.set(statement, "term networks then next policy");
132
+
133
+ this.out.set(statement, "term others then reject");
134
+ }
135
+
136
+ private prefixSets(): void {
137
+ for (const [name, set] of Object.entries(this.device.prefix_sets ?? {})) {
138
+ const list = `policy-options prefix-list ${name}`;
139
+
140
+ if ("prefixes" in set && set.prefixes.length) for (const prefix of set.prefixes) this.out.set(list, prefix);
141
+ else this.out.set(list);
142
+
143
+ if ("source" in set) this.out.note(`${JunosBgp.GENERATED}${list}`);
144
+ }
145
+ }
146
+
147
+ private rpki(): void {
148
+ for (const server of this.device.routing?.rpki ?? []) {
149
+ this.out.set(`routing-options validation group ${JunosBgp.RPKI_GROUP} session`, server.address, "port", server.port);
150
+ }
151
+ }
152
+
153
+ /** The family a rule is limited to, by its match: a family, a prefix, or a prefix set of one family. */
154
+ private familyOfRule(rule: AnyRule): Family | undefined {
155
+ const match = rule.match;
156
+ if (match?.family) return match.family;
157
+
158
+ if (match?.prefix) return Address.family(Address.host(match.prefix));
159
+
160
+ const set = match?.prefix_set ? this.device.prefix_sets?.[match.prefix_set] : undefined;
161
+ if (set && "source" in set) return set.source.family;
162
+
163
+ if (set?.prefixes[0]) return Address.family(Address.host(set.prefixes[0]));
164
+
165
+ return undefined;
166
+ }
167
+
168
+ /** A policy's rules with each call replaced by the rules it calls, in order. A called policy that decides nothing hands back to the caller, as it does on every platform. */
169
+ private flatten(policy: string, label: string, seen: string[]): { label: string; rule: AnyRule }[] {
170
+ const rules = this.device.policies?.[policy] ?? [];
171
+ const flat: { label: string; rule: AnyRule }[] = [];
172
+
173
+ rules.forEach((rule, index) => {
174
+ const here = label ? `${label}.${index + 1}` : `${index + 1}`;
175
+
176
+ if (rule.call && !seen.includes(rule.call)) flat.push(...this.flatten(rule.call, here, [...seen, rule.call]));
177
+
178
+ if (rule.match || rule.set || rule.action) flat.push({ label: here, rule });
179
+ });
180
+
181
+ return flat;
182
+ }
183
+
184
+ /** One policy as a policy statement: a term per rule, then a reject when the last rule does not decide on every route. */
185
+ private policyStatement(policy: string): void {
186
+ const statement = `policy-options policy-statement ${policy}`;
187
+ const flat = this.flatten(policy, "", [policy]);
188
+
189
+ for (const { label, rule } of flat) {
190
+ const family = this.familyOfRule(rule);
191
+ // A next hop only means something to routes of its own family, and one term cannot filter prefixes of both, so a rule for both families splits into a term for each.
192
+ const split = !family && (rule.set?.next_hop || rule.match?.prefix_length) ? (["ipv4", "ipv6"] as const) : [family];
193
+
194
+ for (const part of split) {
195
+ const name =
196
+ split.length > 1
197
+ ? JunosBgp.termName(`${label}-${part === "ipv4" ? "v4" : "v6"}`, rule)
198
+ : JunosBgp.termName(label, rule);
199
+ this.term(`${statement} term ${name}`, rule, part, split.length > 1);
200
+ }
201
+ }
202
+
203
+ const last = flat.at(-1)?.rule;
204
+ const decides = last !== undefined && !last.match && last.action !== undefined;
205
+ if (!decides) this.out.set(statement, "term circuit-default then reject");
206
+ }
207
+
208
+ private term(term: string, rule: AnyRule, family: Family | undefined, splitFamily: boolean): void {
209
+ const match = rule.match ?? {};
210
+ const set = rule.set ?? {};
211
+ const from: (string | number)[][] = [];
212
+ const then: (string | number)[][] = [];
213
+
214
+ if (match.family || splitFamily) from.push(["family", JunosBgp.FAMILY[(match.family ?? family)!]]);
215
+ if (match.prefix) from.push(["route-filter", match.prefix, "exact"]);
216
+ if (match.prefix_set) from.push(["prefix-list-filter", match.prefix_set, "orlonger"]);
217
+
218
+ if (match.prefix_length) {
219
+ const families = family ? [family] : (["ipv4", "ipv6"] as const);
220
+
221
+ for (const each of families) {
222
+ const longest = each === "ipv4" ? 32 : 128;
223
+ const range = `/${match.prefix_length.min ?? 0}-/${match.prefix_length.max ?? longest}`;
224
+ from.push(["route-filter", each === "ipv4" ? "0.0.0.0/0" : "::/0", "prefix-length-range", range]);
225
+ }
226
+ }
227
+
228
+ if (match.as_path_length) {
229
+ const { min = 0, max } = match.as_path_length;
230
+ const name = `circuit-path-length-${min}-${max ?? "any"}`;
231
+ this.out.set(`policy-options as-path ${name}`, JunosRender.quote(`.{${min},${max ?? ""}}`));
232
+ from.push(["as-path", name]);
233
+ }
234
+
235
+ if (match.through_as !== undefined) {
236
+ const name = `circuit-through-${match.through_as}`;
237
+ this.out.set(`policy-options as-path ${name}`, JunosRender.quote(`.* ${match.through_as} .*`));
238
+ from.push(["as-path", name]);
239
+ }
240
+
241
+ if (match.rpki) from.push(["validation-database", JunosBgp.VALIDATION[match.rpki]]);
242
+
243
+ // Members of one named community must all be present, so a rule matching both kinds names them together.
244
+ const wanted = [
245
+ ...(match.community ? [JunosBgp.member(match.community, false)] : []),
246
+ ...(match.large_community ? [JunosBgp.member(match.large_community, true)] : []),
247
+ ];
248
+
249
+ if (wanted.length) from.push(["community", this.community("match", wanted)]);
250
+
251
+ if (set.local_pref !== undefined) then.push(["local-preference", set.local_pref]);
252
+ if (set.med !== undefined) then.push(["metric", set.med]);
253
+
254
+ if (set.prepend !== undefined) {
255
+ const asn = this.device.routing!.bgp!.asn;
256
+ then.push(["as-path-prepend", JunosRender.quote(Array(set.prepend).fill(asn).join(" "))]);
257
+ }
258
+
259
+ // Replacing communities of one kind is removing every one of that kind, then adding the new ones.
260
+ const removed = [
261
+ ...(set.communities ? ["*:*"] : (set.remove_communities ?? [])).map((pattern) => JunosBgp.member(pattern, false)),
262
+ ...(set.large_communities ? ["*:*:*"] : (set.remove_large_communities ?? [])).map((pattern) => JunosBgp.member(pattern, true)),
263
+ ];
264
+
265
+ const added = [
266
+ ...(set.communities ?? set.add_communities ?? []).map((value) => JunosBgp.member(value, false)),
267
+ ...(set.large_communities ?? set.add_large_communities ?? []).map((value) => JunosBgp.member(value, true)),
268
+ ];
269
+
270
+ if (set.communities && set.add_communities) added.push(...set.add_communities.map((value) => JunosBgp.member(value, false)));
271
+ if (set.large_communities && set.add_large_communities)
272
+ added.push(...set.add_large_communities.map((value) => JunosBgp.member(value, true)));
273
+
274
+ for (const pattern of removed) then.push(["community delete", this.community("remove", [pattern])]);
275
+ if (added.length) then.push(["community add", this.community("add", added)]);
276
+
277
+ if (set.next_hop && (!family || Address.family(set.next_hop) === family)) then.push(["next-hop", set.next_hop]);
278
+ if (set.blackhole) then.push(["next-hop discard"]);
279
+ if (rule.action) then.push([rule.action]);
280
+
281
+ for (const words of from) this.out.set(term, "from", ...words);
282
+ for (const words of then) this.out.set(term, "then", ...words);
283
+
284
+ if (!from.length && !then.length) this.out.set(term);
285
+ }
286
+
287
+ /** A named community holding these members, named after them. */
288
+ private community(purpose: string, members: string[]): string {
289
+ const name = `circuit-${purpose}-${JunosBgp.slug(members.join(" "))}`;
290
+ for (const value of members) this.out.set(`policy-options community ${name} members`, JunosRender.quote(value));
291
+ return name;
292
+ }
293
+ }