@takodotid/circuit 0.12.0 → 0.13.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.
@@ -162,14 +162,14 @@ function communityScheme(scheme) {
162
162
  const information = [scheme.learned_from?.function, scheme.learned_at?.function, scheme.learned_from_as].filter((fn) => fn !== undefined);
163
163
  const tag = (neighborClass, neighborAs, options = {}) => {
164
164
  const { site } = options;
165
- const mayAsk = scheme.requests_from?.includes(neighborClass) ?? false;
165
+ const isCustomer = scheme.customers?.includes(neighborClass) ?? false;
166
166
  if (scheme.learned_at && site === undefined)
167
167
  throw new Error(`communityScheme: tag for AS${neighborAs} needs a site, for learned_at`);
168
168
  return {
169
169
  description: `Learned from AS${neighborAs}`,
170
170
  set: {
171
171
  remove_communities: ours.standard,
172
- remove_large_communities: mayAsk ? information.map((fn) => community(fn, "*")) : ours.large,
172
+ remove_large_communities: isCustomer ? information.map((fn) => community(fn, "*")) : ours.large,
173
173
  add_large_communities: [
174
174
  ...scheme.learned_from ? [community(scheme.learned_from.function, scheme.learned_from.classes[neighborClass])] : [],
175
175
  ...scheme.learned_at ? [community(scheme.learned_at.function, site)] : [],
@@ -64,8 +64,8 @@ export type CommunityScheme<Class extends string = string> = {
64
64
  function: number;
65
65
  classes: Readonly<Record<Class, number>>;
66
66
  };
67
- /** The kinds of neighbor allowed to ask you for something, usually your customers: the requests below that they set on their routes stay. From anyone else, they are removed on import. */
68
- requests_from?: readonly NoInfer<Class>[];
67
+ /** Which of the classes above are your customers. Only a customer may ask you for something, such as not announcing its route somewhere; what anyone else asks is removed when the route comes in. */
68
+ customers?: readonly NoInfer<Class>[];
69
69
  /** Set on import: the site a route was learned at, `asn:function:site`. `sites` names the sites, for the catalogue. */
70
70
  learned_at?: {
71
71
  function: number;
@@ -87,7 +87,7 @@ export type CommunityScheme<Class extends string = string> = {
87
87
  /**
88
88
  * 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.
89
89
  *
90
- * - `tag(neighborClass, neighborAs, { site })`: a rule for an import policy. It removes your communities a neighbor set, then adds where the route was learned. A neighbor of a class in `requests_from` keeps its requests. `site` is needed when the scheme has `learned_at`.
90
+ * - `tag(neighborClass, neighborAs, { site })`: a rule for an import policy. It removes your communities a neighbor set, then adds where the route was learned. A customer, a class in `customers`, keeps its requests. `site` is needed when the scheme has `learned_at`.
91
91
  * - `actions(neighborAs, { blackhole })`: 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. A blackholed route goes no further, unless `blackhole` names the neighbor's own blackhole community: then it is passed on with it, so the neighbor drops the traffic too.
92
92
  * - `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.
93
93
  * - `catalogue`: every community, for `communities` in `defineNetwork`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takodotid/circuit",
3
- "version": "0.12.0",
3
+ "version": "0.13.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",
@@ -226,8 +226,8 @@ export type CommunityScheme<Class extends string = string> = {
226
226
  asn: number;
227
227
  /** Set on import: the kind of neighbor a route came from, `asn:function:class`. */
228
228
  learned_from?: { function: number; classes: Readonly<Record<Class, number>> };
229
- /** The kinds of neighbor allowed to ask you for something, usually your customers: the requests below that they set on their routes stay. From anyone else, they are removed on import. */
230
- requests_from?: readonly NoInfer<Class>[];
229
+ /** Which of the classes above are your customers. Only a customer may ask you for something, such as not announcing its route somewhere; what anyone else asks is removed when the route comes in. */
230
+ customers?: readonly NoInfer<Class>[];
231
231
  /** Set on import: the site a route was learned at, `asn:function:site`. `sites` names the sites, for the catalogue. */
232
232
  learned_at?: { function: number; sites?: Readonly<Record<number, string>> };
233
233
  /** Set on import: the AS a route was learned from, `asn:function:asn`. */
@@ -251,7 +251,7 @@ const article = (word: string) => (/^[aeiou]/i.test(word) ? `an ${word}` : `a ${
251
251
  /**
252
252
  * 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.
253
253
  *
254
- * - `tag(neighborClass, neighborAs, { site })`: a rule for an import policy. It removes your communities a neighbor set, then adds where the route was learned. A neighbor of a class in `requests_from` keeps its requests. `site` is needed when the scheme has `learned_at`.
254
+ * - `tag(neighborClass, neighborAs, { site })`: a rule for an import policy. It removes your communities a neighbor set, then adds where the route was learned. A customer, a class in `customers`, keeps its requests. `site` is needed when the scheme has `learned_at`.
255
255
  * - `actions(neighborAs, { blackhole })`: 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. A blackholed route goes no further, unless `blackhole` names the neighbor's own blackhole community: then it is passed on with it, so the neighbor drops the traffic too.
256
256
  * - `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.
257
257
  * - `catalogue`: every community, for `communities` in `defineNetwork`.
@@ -267,7 +267,7 @@ export function communityScheme<const Class extends string>(scheme: CommunitySch
267
267
 
268
268
  const tag = (neighborClass: Class, neighborAs: number, options: { site?: number } = {}): PolicyRule<never, never> => {
269
269
  const { site } = options;
270
- const mayAsk = scheme.requests_from?.includes(neighborClass) ?? false;
270
+ const isCustomer = scheme.customers?.includes(neighborClass) ?? false;
271
271
  if (scheme.learned_at && site === undefined)
272
272
  throw new Error(`communityScheme: tag for AS${neighborAs} needs a site, for learned_at`);
273
273
 
@@ -275,7 +275,7 @@ export function communityScheme<const Class extends string>(scheme: CommunitySch
275
275
  description: `Learned from AS${neighborAs}`,
276
276
  set: {
277
277
  remove_communities: ours.standard,
278
- remove_large_communities: mayAsk ? information.map((fn) => community(fn, "*")) : ours.large,
278
+ remove_large_communities: isCustomer ? information.map((fn) => community(fn, "*")) : ours.large,
279
279
  add_large_communities: [
280
280
  ...(scheme.learned_from ? [community(scheme.learned_from.function, scheme.learned_from.classes[neighborClass])] : []),
281
281
  ...(scheme.learned_at ? [community(scheme.learned_at.function, site!)] : []),
@@ -1,4 +1,6 @@
1
1
  // The edge router: BGP with an IP transit and an internet exchange, the gateway of every tenant, and the filter between them.
2
+ //
3
+ // Hurricane Electric and SGIX stand for your own transit and exchange. Addresses are from the documentation ranges; use the ones your providers give you.
2
4
 
3
5
  import type { Prefix } from "@takodotid/circuit";
4
6
  import { defineDevice, merge, secret } from "@takodotid/circuit";
@@ -29,16 +31,16 @@ export default defineDevice({
29
31
  vlans: merge(
30
32
  {
31
33
  mgmt: { id: 99, description: "Management of the switches" },
32
- transit: { id: 300, description: "IP transit from Example Transit, AS64501" },
33
- ix: { id: 200, description: "Peering LAN of Example IX" },
34
+ transit: { id: 300, description: "IP transit from Hurricane Electric, AS6939" },
35
+ ix: { id: 200, description: "Peering LAN of SGIX" },
34
36
  },
35
37
  internetVlans(TENANTS)
36
38
  ),
37
39
 
38
40
  ports: {
39
41
  "1g-1": { description: "Management", addresses: ["10.0.0.1/24"] },
40
- "25g-1": { description: "Example Transit", access_vlan: "transit", acl: "edge" },
41
- "25g-2": { description: "Example IX", access_vlan: "ix", acl: "edge" },
42
+ "25g-1": { description: "Hurricane Electric", access_vlan: "transit", acl: "edge" },
43
+ "25g-2": { description: "SGIX", access_vlan: "ix", acl: "edge" },
42
44
  "100g-1": {
43
45
  description: "tor-01",
44
46
  speed: "40g",
@@ -64,25 +66,25 @@ export default defineDevice({
64
66
  asn: ASN,
65
67
  networks: [...OURS],
66
68
  neighbors: {
67
- "example-transit": {
69
+ "hurricane-electric": {
68
70
  address: "203.0.113.1",
69
- remote_as: 64501,
71
+ remote_as: 6939,
70
72
  role: "provider",
71
73
  import: "TRANSIT-IN",
72
74
  export: "OUT",
73
75
  max_prefixes: 1_200_000,
74
76
  },
75
- "example-ix-rs-v4": {
77
+ "sgix-rs-v4": {
76
78
  address: "192.0.2.1",
77
- remote_as: 64502,
79
+ remote_as: 55518,
78
80
  role: "rs",
79
81
  import: "IX-IN",
80
82
  export: "OUT",
81
83
  max_prefixes: 100_000,
82
84
  },
83
- "example-ix-rs-v6": {
85
+ "sgix-rs-v6": {
84
86
  address: "2001:db8:ffff::1",
85
- remote_as: 64502,
87
+ remote_as: 55518,
86
88
  role: "rs",
87
89
  import: "IX-IN",
88
90
  export: "OUT",
@@ -95,8 +97,8 @@ export default defineDevice({
95
97
  policies: {
96
98
  SANITY: bgpSanity(),
97
99
  // A route from the exchange is preferred over the same route from the transit: higher local preference wins.
98
- "TRANSIT-IN": [{ call: "SANITY" }, communities.tag("transit", 64501), { set: { local_pref: 100 } }, { action: "accept" }],
99
- "IX-IN": [{ call: "SANITY" }, communities.tag("exchange", 64502), { set: { local_pref: 200 } }, { action: "accept" }],
100
+ "TRANSIT-IN": [{ call: "SANITY" }, communities.tag("transit", 6939), { set: { local_pref: 100 } }, { action: "accept" }],
101
+ "IX-IN": [{ call: "SANITY" }, communities.tag("exchange", 55518), { set: { local_pref: 200 } }, { action: "accept" }],
100
102
  OUT: [
101
103
  ...OURS.map((prefix) => ({ description: `Announce ${prefix}`, match: { prefix }, action: "accept" as const })),
102
104
  { description: "Nothing else leaves", action: "reject" },
@@ -1,4 +1,6 @@
1
1
  // The edge router: BGP with an IP transit and an internet exchange, filtered on the way in and on the way out.
2
+ //
3
+ // Hurricane Electric and SGIX stand for your own transit and exchange. Addresses are from the documentation ranges; use the ones your providers give you.
2
4
 
3
5
  import type { Prefix } from "@takodotid/circuit";
4
6
  import { defineDevice, secret } from "@takodotid/circuit";
@@ -25,14 +27,14 @@ export default defineDevice({
25
27
  management: { allow: [MANAGEMENT], ssh: {} },
26
28
 
27
29
  vlans: {
28
- transit: { id: 300, description: "IP transit from Example Transit, AS64501" },
29
- ix: { id: 200, description: "Peering LAN of Example IX" },
30
+ transit: { id: 300, description: "IP transit from Hurricane Electric, AS6939" },
31
+ ix: { id: 200, description: "Peering LAN of SGIX" },
30
32
  },
31
33
 
32
34
  ports: {
33
35
  "1g-1": { description: "Management", addresses: ["10.0.0.1/24"] },
34
- "25g-1": { description: "Example Transit", access_vlan: "transit", acl: "edge" },
35
- "25g-2": { description: "Example IX", access_vlan: "ix", acl: "edge" },
36
+ "25g-1": { description: "Hurricane Electric", access_vlan: "transit", acl: "edge" },
37
+ "25g-2": { description: "SGIX", access_vlan: "ix", acl: "edge" },
36
38
  },
37
39
 
38
40
  interfaces: {
@@ -48,25 +50,25 @@ export default defineDevice({
48
50
  asn: ASN,
49
51
  networks: [...OURS],
50
52
  neighbors: {
51
- "example-transit": {
53
+ "hurricane-electric": {
52
54
  address: "203.0.113.1",
53
- remote_as: 64501,
55
+ remote_as: 6939,
54
56
  role: "provider",
55
57
  import: "TRANSIT-IN",
56
58
  export: "OUT",
57
59
  max_prefixes: 1_200_000,
58
60
  },
59
- "example-ix-rs-v4": {
61
+ "sgix-rs-v4": {
60
62
  address: "192.0.2.1",
61
- remote_as: 64502,
63
+ remote_as: 55518,
62
64
  role: "rs",
63
65
  import: "IX-IN",
64
66
  export: "OUT",
65
67
  max_prefixes: 100_000,
66
68
  },
67
- "example-ix-rs-v6": {
69
+ "sgix-rs-v6": {
68
70
  address: "2001:db8:ffff::1",
69
- remote_as: 64502,
71
+ remote_as: 55518,
70
72
  role: "rs",
71
73
  import: "IX-IN",
72
74
  export: "OUT",
@@ -79,8 +81,8 @@ export default defineDevice({
79
81
  policies: {
80
82
  SANITY: bgpSanity(),
81
83
  // A route from the exchange is preferred over the same route from the transit: higher local preference wins.
82
- "TRANSIT-IN": [{ call: "SANITY" }, communities.tag("transit", 64501), { set: { local_pref: 100 } }, { action: "accept" }],
83
- "IX-IN": [{ call: "SANITY" }, communities.tag("exchange", 64502), { set: { local_pref: 200 } }, { action: "accept" }],
84
+ "TRANSIT-IN": [{ call: "SANITY" }, communities.tag("transit", 6939), { set: { local_pref: 100 } }, { action: "accept" }],
85
+ "IX-IN": [{ call: "SANITY" }, communities.tag("exchange", 55518), { set: { local_pref: 200 } }, { action: "accept" }],
84
86
  OUT: [
85
87
  ...OURS.map((prefix) => ({ description: `Announce ${prefix}`, match: { prefix }, action: "accept" as const })),
86
88
  { description: "Nothing else leaves", action: "reject" },