@takodotid/circuit 0.7.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 +1 -1
- package/src/adapters/devices/routeros/TRAPS.md +2 -0
- package/src/adapters/devices/routeros/render.ts +7 -5
- package/src/adapters/registries/peeringdb.ts +2 -2
- package/src/presets/index.ts +27 -1
- package/src/schema/policy.ts +2 -0
- package/src/schema/routing.ts +5 -3
- package/templates/colocation/edge-01.ts +3 -3
- package/templates/edge-router/edge-01.ts +3 -3
package/package.json
CHANGED
|
@@ -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.
|
|
@@ -874,12 +874,13 @@ export const RPKI_GROUP = "rpki";
|
|
|
874
874
|
/** The chain a session without a policy uses, so that it learns or announces nothing. */
|
|
875
875
|
export const REJECT_ALL = "REJECT-ALL";
|
|
876
876
|
|
|
877
|
+
// RouterOS takes our own role. The config states the neighbor's, so each becomes its opposite: a neighbor that is our provider makes us its customer.
|
|
877
878
|
const ROLES = {
|
|
878
|
-
provider: "ebgp-
|
|
879
|
-
customer: "ebgp-
|
|
879
|
+
provider: "ebgp-customer",
|
|
880
|
+
customer: "ebgp-provider",
|
|
880
881
|
peer: "ebgp-peer",
|
|
881
|
-
rs: "ebgp-rs",
|
|
882
|
-
"rs-client": "ebgp-rs
|
|
882
|
+
rs: "ebgp-rs-client",
|
|
883
|
+
"rs-client": "ebgp-rs",
|
|
883
884
|
} as const;
|
|
884
885
|
|
|
885
886
|
type AnyRule = PolicyRule<string, string>;
|
|
@@ -1005,6 +1006,7 @@ function actions(rule: AnyRule, family: Family): string[] {
|
|
|
1005
1006
|
// An address only means something to routes of its own family.
|
|
1006
1007
|
if (set.next_hop && familyOf(set.next_hop) === family) steps.push(`set gw ${set.next_hop}`);
|
|
1007
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");
|
|
1008
1010
|
if (rule.action) steps.push(rule.action);
|
|
1009
1011
|
return steps;
|
|
1010
1012
|
}
|
|
@@ -1097,7 +1099,7 @@ function bgpConnections(out: Output, ctx: Context): void {
|
|
|
1097
1099
|
const addressFamily = familyOf(neighbor.address);
|
|
1098
1100
|
const internal = settings.remote_as === bgp.asn;
|
|
1099
1101
|
const originates = bgp.networks?.some((prefix) => familyOf(host(prefix)) === family);
|
|
1100
|
-
const role = settings.
|
|
1102
|
+
const role = settings.role ? ROLES[settings.role] : internal ? "ibgp" : "ebgp";
|
|
1101
1103
|
|
|
1102
1104
|
out.add(
|
|
1103
1105
|
"/routing bgp connection",
|
|
@@ -56,8 +56,8 @@ function peersWithRouteServer(device: Device, iface: AnyInterface): boolean {
|
|
|
56
56
|
|
|
57
57
|
return Object.values(bgp?.neighbors ?? {}).some((neighbor) => {
|
|
58
58
|
const group = neighbor.group ? bgp?.groups?.[neighbor.group] : undefined;
|
|
59
|
-
const role = neighbor.
|
|
60
|
-
return role === "rs
|
|
59
|
+
const role = neighbor.role ?? group?.role;
|
|
60
|
+
return role === "rs" && subnets.some((subnet) => contains(subnet, neighbor.address));
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
|
package/src/presets/index.ts
CHANGED
|
@@ -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
|
}
|
package/src/schema/policy.ts
CHANGED
|
@@ -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
|
/**
|
package/src/schema/routing.ts
CHANGED
|
@@ -18,7 +18,9 @@ export type StaticRoute<I extends string, F extends string> = {
|
|
|
18
18
|
description?: string;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* 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.
|
|
23
|
+
*/
|
|
22
24
|
export type BgpRole = "provider" | "customer" | "peer" | "rs" | "rs-client";
|
|
23
25
|
|
|
24
26
|
/** Settings a BGP neighbor can state itself or take from its group. */
|
|
@@ -33,8 +35,8 @@ export type NeighborSettings<P extends string> = {
|
|
|
33
35
|
import?: P;
|
|
34
36
|
/** Policy for routes announced. Nothing is announced when absent. */
|
|
35
37
|
export?: P;
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
+
/** What the neighbor is to us, such as `provider` for a transit. Both sides then reject a route leak, after RFC 9234. */
|
|
39
|
+
role?: BgpRole;
|
|
38
40
|
/** The neighbor is more than one hop away. */
|
|
39
41
|
multihop?: boolean;
|
|
40
42
|
/** TCP MD5 password shared with the neighbor. */
|
|
@@ -67,7 +67,7 @@ export default defineDevice({
|
|
|
67
67
|
"example-transit": {
|
|
68
68
|
address: "203.0.113.1",
|
|
69
69
|
remote_as: 64501,
|
|
70
|
-
|
|
70
|
+
role: "provider",
|
|
71
71
|
import: "TRANSIT-IN",
|
|
72
72
|
export: "OUT",
|
|
73
73
|
max_prefixes: 1_200_000,
|
|
@@ -75,7 +75,7 @@ export default defineDevice({
|
|
|
75
75
|
"example-ix-rs-v4": {
|
|
76
76
|
address: "192.0.2.1",
|
|
77
77
|
remote_as: 64502,
|
|
78
|
-
|
|
78
|
+
role: "rs",
|
|
79
79
|
import: "IX-IN",
|
|
80
80
|
export: "OUT",
|
|
81
81
|
max_prefixes: 100_000,
|
|
@@ -83,7 +83,7 @@ export default defineDevice({
|
|
|
83
83
|
"example-ix-rs-v6": {
|
|
84
84
|
address: "2001:db8:ffff::1",
|
|
85
85
|
remote_as: 64502,
|
|
86
|
-
|
|
86
|
+
role: "rs",
|
|
87
87
|
import: "IX-IN",
|
|
88
88
|
export: "OUT",
|
|
89
89
|
max_prefixes: 50_000,
|
|
@@ -51,7 +51,7 @@ export default defineDevice({
|
|
|
51
51
|
"example-transit": {
|
|
52
52
|
address: "203.0.113.1",
|
|
53
53
|
remote_as: 64501,
|
|
54
|
-
|
|
54
|
+
role: "provider",
|
|
55
55
|
import: "TRANSIT-IN",
|
|
56
56
|
export: "OUT",
|
|
57
57
|
max_prefixes: 1_200_000,
|
|
@@ -59,7 +59,7 @@ export default defineDevice({
|
|
|
59
59
|
"example-ix-rs-v4": {
|
|
60
60
|
address: "192.0.2.1",
|
|
61
61
|
remote_as: 64502,
|
|
62
|
-
|
|
62
|
+
role: "rs",
|
|
63
63
|
import: "IX-IN",
|
|
64
64
|
export: "OUT",
|
|
65
65
|
max_prefixes: 100_000,
|
|
@@ -67,7 +67,7 @@ export default defineDevice({
|
|
|
67
67
|
"example-ix-rs-v6": {
|
|
68
68
|
address: "2001:db8:ffff::1",
|
|
69
69
|
remote_as: 64502,
|
|
70
|
-
|
|
70
|
+
role: "rs",
|
|
71
71
|
import: "IX-IN",
|
|
72
72
|
export: "OUT",
|
|
73
73
|
max_prefixes: 50_000,
|