@takodotid/circuit 1.0.0-rc.1 → 1.0.0-rc.3
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/README.md +3 -3
- package/package.json +1 -1
- package/src/checks/device.ts +48 -5
- package/src/cli/app.ts +6 -2
- package/src/cli/editor-types.generated.ts +5 -5
- package/src/cli/index.ts +2 -0
- package/src/cli/releases.ts +1 -1
- package/src/cli/setup.ts +10 -6
- package/src/cli/version.ts +1 -1
- package/src/config/define.ts +6 -2
- package/src/config/device.ts +2 -2
- package/src/config/firewall.ts +14 -0
- package/src/config/interfaces.ts +50 -4
- package/src/config/keys.ts +8 -4
- package/src/model.ts +4 -2
- package/src/platforms/derive.ts +30 -1
- package/src/platforms/fortios/TRAPS.md +1 -0
- package/src/platforms/fortios/index.ts +6 -5
- package/src/platforms/fortios/plan.ts +22 -0
- package/src/platforms/fortios/render.ts +19 -0
- package/src/platforms/junos/index.ts +1 -0
- package/src/platforms/raisecom-ros/index.ts +1 -0
- package/src/platforms/routeros/TRAPS.md +4 -0
- package/src/platforms/routeros/export.ts +6 -0
- package/src/platforms/routeros/index.ts +1 -0
- package/src/platforms/routeros/plan.ts +6 -0
- package/src/platforms/routeros/render.ts +85 -10
- package/src/platforms/vrp/index.ts +1 -0
- package/src/project/project.ts +5 -2
package/README.md
CHANGED
|
@@ -88,9 +88,9 @@ In the editor, `"10g-49"` is an error, `10g-49 is not a port of CE6855-48S6Q-HI`
|
|
|
88
88
|
|
|
89
89
|
## Start
|
|
90
90
|
|
|
91
|
-
1. Install Circuit:
|
|
92
|
-
- Linux and macOS: `curl -fsSL https://circuit.
|
|
93
|
-
- Windows, in PowerShell: `irm https://circuit.
|
|
91
|
+
1. Install Circuit. Circuit v1 is a release candidate, so its installers come from its GitHub release until circuit.tako.id serves v1:
|
|
92
|
+
- Linux and macOS: `curl -fsSL https://github.com/takodotid/circuit/releases/download/v1.0.0-rc.2/install | sh`
|
|
93
|
+
- Windows, in PowerShell: `irm https://github.com/takodotid/circuit/releases/download/v1.0.0-rc.2/install.ps1 | iex`
|
|
94
94
|
2. Start a project: `circuit new network`. Open its folder in VS Code. Add a site in `circuit.ts`, then write each device in `sites/<site>/<device>.ts`.
|
|
95
95
|
3. Run `circuit`. It opens a menu, or runs a command straight away, such as `circuit validate`.
|
|
96
96
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takodotid/circuit",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.3",
|
|
4
4
|
"description": "Declarative, vendor-neutral network configuration: describe each device in TypeScript, and Circuit makes the device match. One binary, nothing to install.",
|
|
5
5
|
"homepage": "https://circuit.tako.id",
|
|
6
6
|
"bugs": {
|
package/src/checks/device.ts
CHANGED
|
@@ -77,7 +77,7 @@ export class DeviceChecks {
|
|
|
77
77
|
DeviceChecks.interfaces(device, ports, report, ip);
|
|
78
78
|
DeviceChecks.routing(device, report, cidr, ip);
|
|
79
79
|
DeviceChecks.policies(device, report, cidr);
|
|
80
|
-
DeviceChecks.firewall(device, cidr);
|
|
80
|
+
DeviceChecks.firewall(device, report, cidr);
|
|
81
81
|
DeviceChecks.services(device, report, cidr);
|
|
82
82
|
DeviceChecks.certificates(device, report);
|
|
83
83
|
DeviceChecks.vdoms(device, report, cidr, ip);
|
|
@@ -217,17 +217,53 @@ export class DeviceChecks {
|
|
|
217
217
|
const where = `interfaces.${kind}.${name}`;
|
|
218
218
|
if (ports.includes(name)) report(`${where} has the name of a port`);
|
|
219
219
|
|
|
220
|
-
if (kind === "gre" || kind === "vxlan") {
|
|
220
|
+
if (kind === "gre" || kind === "vxlan" || kind === "eoip") {
|
|
221
221
|
const tunnel = settings as Extract<Interface, { remote_address: string }>;
|
|
222
222
|
ip(where, tunnel.local_address);
|
|
223
223
|
ip(where, tunnel.remote_address);
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
if (kind === "eoip") {
|
|
227
|
+
const id = (settings as Extract<Interface, { tunnel_id: number }>).tunnel_id;
|
|
228
|
+
if (!(Number.isInteger(id) && id >= 0 && id <= 65535))
|
|
229
|
+
report(`${where}.tunnel_id ${id} is not a whole number from 0 to 65535`);
|
|
230
|
+
|
|
231
|
+
const same = Object.entries(device.interfaces?.eoip ?? {}).find(
|
|
232
|
+
([other, tunnel]) => other !== name && tunnel?.tunnel_id === id
|
|
233
|
+
);
|
|
234
|
+
if (same && same[0] > name) report(`${where}.tunnel_id ${id} is also interfaces.eoip.${same[0]}'s`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (settings.shaping && !(settings.shaping.mbps > 0)) report(`${where}.shaping.mbps ${settings.shaping.mbps} is not above 0`);
|
|
238
|
+
|
|
239
|
+
if (kind === "vlan_interface") {
|
|
240
|
+
const subnets = (settings.addresses ?? []).map((entry) => DeviceChecks.addressOf(entry));
|
|
241
|
+
|
|
242
|
+
for (const address of (settings as Extract<Interface, { vlan: string }>).proxy_arp ?? []) {
|
|
243
|
+
ip(`${where}.proxy_arp`, address);
|
|
244
|
+
if (!subnets.some((subnet) => Address.isCidr(subnet) && Address.contains(subnet, address)))
|
|
245
|
+
report(`${where}.proxy_arp ${address} is in none of its subnets, so no host there asks for it`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
226
249
|
if (kind === "lag") {
|
|
250
|
+
const lag = settings as Extract<Interface, { mode?: string }> & {
|
|
251
|
+
link_check?: { arp_targets: readonly string[]; interval_ms: number };
|
|
252
|
+
};
|
|
227
253
|
DeviceChecks.frames(where, settings as Switched, report);
|
|
228
254
|
|
|
229
|
-
const
|
|
230
|
-
|
|
255
|
+
const ports = Derive.ports(device).filter(([, port]) => port.in_lag === name);
|
|
256
|
+
const tunnels = Object.entries(device.interfaces?.eoip ?? {}).filter(([, tunnel]) => tunnel?.in_lag === name);
|
|
257
|
+
if (!ports.length && !tunnels.length) report(`${where} has no members`);
|
|
258
|
+
if (tunnels.length && lag.mode !== "round_robin") report(`${where} has EoIP members, which only a round_robin LAG takes`);
|
|
259
|
+
|
|
260
|
+
const check = lag.link_check;
|
|
261
|
+
if (check) {
|
|
262
|
+
if (!check.arp_targets.length) report(`${where}.link_check.arp_targets is empty`);
|
|
263
|
+
for (const target of check.arp_targets) ip(`${where}.link_check.arp_targets`, target);
|
|
264
|
+
if (!(Number.isInteger(check.interval_ms) && check.interval_ms > 0))
|
|
265
|
+
report(`${where}.link_check.interval_ms ${check.interval_ms} is not a whole number above 0`);
|
|
266
|
+
}
|
|
231
267
|
}
|
|
232
268
|
|
|
233
269
|
if (kind === "bridge") {
|
|
@@ -355,7 +391,7 @@ export class DeviceChecks {
|
|
|
355
391
|
for (const policy of Object.keys(policies)) visit(policy, []);
|
|
356
392
|
}
|
|
357
393
|
|
|
358
|
-
private static firewall(device: Device, cidr: Checker): void {
|
|
394
|
+
private static firewall(device: Device, report: Report, cidr: Checker): void {
|
|
359
395
|
const firewall = device.firewall;
|
|
360
396
|
|
|
361
397
|
for (const [name, members] of Object.entries(firewall?.address_sets ?? {})) {
|
|
@@ -371,8 +407,15 @@ export class DeviceChecks {
|
|
|
371
407
|
})),
|
|
372
408
|
{ where: "firewall.nat.source", rules: firewall?.nat?.source ?? [] },
|
|
373
409
|
{ where: "firewall.nat.destination", rules: firewall?.nat?.destination ?? [] },
|
|
410
|
+
{ where: "firewall.tcp_mss", rules: firewall?.tcp_mss ?? [] },
|
|
374
411
|
];
|
|
375
412
|
|
|
413
|
+
(firewall?.tcp_mss ?? []).forEach((rule, index) => {
|
|
414
|
+
// 536 is the MSS every IPv4 host takes (RFC 879); 65495 is the largest IP packet less 40 bytes of headers.
|
|
415
|
+
if (rule.mss !== "path_mtu" && !(Number.isInteger(rule.mss) && rule.mss >= 536 && rule.mss <= 65495))
|
|
416
|
+
report(`firewall.tcp_mss[${index}].mss ${rule.mss} is not "path_mtu" or a whole number from 536 to 65495`);
|
|
417
|
+
});
|
|
418
|
+
|
|
376
419
|
for (const { where, rules } of chains) {
|
|
377
420
|
for (const rule of rules) {
|
|
378
421
|
// The model holds one prefix a side; a list is taken as the language writes it.
|
package/src/cli/app.ts
CHANGED
|
@@ -42,11 +42,15 @@ export class App {
|
|
|
42
42
|
App.removeFromWindowsPath(folder);
|
|
43
43
|
spawnSync("reg", ["delete", App.UNINSTALL_KEY, "/f"], { stdio: "ignore" });
|
|
44
44
|
|
|
45
|
-
// Windows does not let a running program delete itself, so a separate process deletes it once this one has ended
|
|
46
|
-
|
|
45
|
+
// Windows does not let a running program delete itself, so a separate process deletes it once this one has ended: it tries
|
|
46
|
+
// again until the file goes, for about ten seconds. Only cmd's own commands: a program it started, such as ping to wait,
|
|
47
|
+
// would open a console window. The line goes to cmd as written; quoted the usual way, cmd can't read the paths.
|
|
48
|
+
const remove = `del /f /q "${binary}" "${binary}.old" 2>nul & if not exist "${binary}" (rmdir "${folder}" 2>nul & exit)`;
|
|
49
|
+
spawn("cmd", ["/d", "/c", `for /l %i in (1,1,10000) do @(${remove})`], {
|
|
47
50
|
detached: true,
|
|
48
51
|
stdio: "ignore",
|
|
49
52
|
windowsHide: true,
|
|
53
|
+
windowsVerbatimArguments: true,
|
|
50
54
|
}).unref();
|
|
51
55
|
} else {
|
|
52
56
|
rmSync(binary, { force: true });
|