@takodotid/circuit 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takodotid/circuit",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Declarative, vendor-neutral network configuration: describe each device in TypeScript, and Circuit makes the device match. Alpha.",
5
5
  "keywords": [
6
6
  "network",
@@ -31,3 +31,5 @@ Behaviour that looks like something else. Each one has been hit on a real device
31
31
  - `:find` on an array returns a number or `nil`. Globals persist between exec commands of one login.
32
32
  - A fixed port speed is named by medium, `speed=10G-baseSR-LR` for an optic or `speed=1G-baseT-full` for copper, and only holds with `auto-negotiation=no`. With auto-negotiation back on, the export no longer prints the speed, so the plan resets only auto-negotiation. `speed=""` is refused as ambiguous and `unset speed` does not exist.
33
33
  - `bgp-path-len` counts every AS in the path, prepends included, and takes the same strict comparisons as `dst-len`.
34
+ - `:parse` checks the command, not the text inside `rule=`. A routing filter rule is only checked when it is added.
35
+ - `/routing/filter/filter-wizard` adds the rule it builds; it is not a dry run. `set blackhole yes` was proved this way on 7.23, in an unused chain removed right after.
@@ -1006,6 +1006,7 @@ function actions(rule: AnyRule, family: Family): string[] {
1006
1006
  // An address only means something to routes of its own family.
1007
1007
  if (set.next_hop && familyOf(set.next_hop) === family) steps.push(`set gw ${set.next_hop}`);
1008
1008
  if (set.preferred_source && familyOf(set.preferred_source) === family) steps.push(`set pref-src ${set.preferred_source}`);
1009
+ if (set.blackhole) steps.push("set blackhole yes");
1009
1010
  if (rule.action) steps.push(rule.action);
1010
1011
  return steps;
1011
1012
  }
@@ -223,6 +223,8 @@ export type CommunityScheme<Class extends string = string> = {
223
223
  do_not_announce?: number;
224
224
  /** Set by a customer: prepend toward an AS once, twice or three times, `asn:function:asn`. */
225
225
  prepend?: { once?: number; twice?: number; three_times?: number };
226
+ /** Set by a customer: drop traffic to the route in your network, before it reaches them, `asn:function:0`. The route is never announced further. */
227
+ blackhole?: number;
226
228
  };
227
229
 
228
230
  const PREPENDS = [
@@ -238,6 +240,7 @@ const article = (word: string) => (/^[aeiou]/i.test(word) ? `an ${word}` : `a ${
238
240
  *
239
241
  * - `tag(neighborClass, neighborAs, { site, keepActions })`: a rule for an import policy. It removes your communities a neighbor set, then adds where the route was learned. `site` is needed when the scheme has `learned_at`. With `keepActions`, for a customer, its action communities stay.
240
242
  * - `actions(neighborAs)`: rules for an export policy toward one neighbor. They act on a customer's action communities, then remove all of yours. Place them before the rules that accept.
243
+ * - `blackhole(prefixSet)`: a rule for a customer's import policy, before anything that rejects long prefixes. It takes a route the customer asked to blackhole, but only inside `prefixSet`, its own space, and drops traffic to it here.
241
244
  * - `catalogue`: every community, for `communities` in `defineNetwork`.
242
245
  */
243
246
  export function communityScheme<const Class extends string>(scheme: CommunityScheme<Class>) {
@@ -272,7 +275,27 @@ export function communityScheme<const Class extends string>(scheme: CommunitySch
272
275
  };
273
276
  };
274
277
 
278
+ const blackhole = <S extends string>(prefixSet: S): PolicyRule<never, S> => {
279
+ if (scheme.blackhole === undefined) throw new Error("communityScheme: blackhole needs a function number in the scheme");
280
+
281
+ return {
282
+ description: "Blackhole, asked by the customer",
283
+ match: { large_community: community(scheme.blackhole, 0), prefix_set: prefixSet },
284
+ set: { blackhole: true },
285
+ action: "accept",
286
+ };
287
+ };
288
+
275
289
  const actions = (neighborAs: number): PolicyRule<never, never>[] => [
290
+ ...(scheme.blackhole !== undefined
291
+ ? [
292
+ {
293
+ description: "Blackholed here, never announced",
294
+ match: { large_community: community(scheme.blackhole, 0) },
295
+ action: "reject" as const,
296
+ },
297
+ ]
298
+ : []),
276
299
  ...(scheme.do_not_announce !== undefined
277
300
  ? [
278
301
  {
@@ -332,7 +355,10 @@ export function communityScheme<const Class extends string>(scheme: CommunitySch
332
355
  const fn = scheme.prepend?.[name];
333
356
  return fn === undefined ? [] : [{ community: community(fn, "nnn"), description: `Prepend ${name.replace("_", " ")} to AS$0` }];
334
357
  }),
358
+ ...(scheme.blackhole !== undefined
359
+ ? [{ community: community(scheme.blackhole, 0), description: `Blackhole: dropped in AS${asn}, not announced further` }]
360
+ : []),
335
361
  ];
336
362
 
337
- return { community, tag, actions, catalogue };
363
+ return { community, tag, actions, blackhole, catalogue };
338
364
  }
@@ -64,6 +64,8 @@ export type Sets = {
64
64
  next_hop?: IP;
65
65
  /** Source address for traffic the device itself sends to the route. Applies to routes of the same family as the address. */
66
66
  preferred_source?: IP;
67
+ /** Drop traffic to the route on this device, instead of forwarding it. */
68
+ blackhole?: true;
67
69
  };
68
70
 
69
71
  /**