@takodotid/circuit 0.24.0 → 0.25.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/dist/cli/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  addressesOf,
12
12
  freeOfLength,
13
13
  studioData
14
- } from "../chunks/index-n4zpd25n.js";
14
+ } from "../chunks/index-wm9ypkjq.js";
15
15
  import {
16
16
  familyOf2,
17
17
  split,
@@ -110,20 +110,6 @@ var MARKER = /<secret:([A-Za-z0-9_./:-]+)>/g;
110
110
  var substitute = (text) => text.replace(MARKER, (_, reference) => valueOf(reference));
111
111
  var valueOfReference = valueOf;
112
112
 
113
- // src/core/time.ts
114
- function offset(zone) {
115
- const name = new Intl.DateTimeFormat("en-US", {
116
- timeZone: zone,
117
- timeZoneName: "longOffset"
118
- }).formatToParts(new Date).find((entry) => entry.type === "timeZoneName").value;
119
- const found = /GMT([+-])(\d{2}):(\d{2})/.exec(name);
120
- if (!found)
121
- return 0;
122
- const [, sign, hours, minutes] = found;
123
- const sizeInMinutes = Number(hours) * 60 + Number(minutes);
124
- return sign === "-" ? -sizeInMinutes : sizeInMinutes;
125
- }
126
-
127
113
  // src/transport/ssh.ts
128
114
  import { Client } from "ssh2";
129
115
  function connect(login) {
@@ -264,6 +250,1588 @@ function switchUnsupported(device) {
264
250
  return found;
265
251
  }
266
252
 
253
+ // src/adapters/devices/junos/render.ts
254
+ var USER_CLASS = { admin: "super-user", operator: "operator", "read-only": "read-only" };
255
+ var KEY_STATEMENT = {
256
+ "ssh-ed25519": "ssh-ed25519",
257
+ "ssh-rsa": "ssh-rsa",
258
+ "ecdsa-sha2-nistp256": "ssh-ecdsa",
259
+ "ecdsa-sha2-nistp384": "ssh-ecdsa",
260
+ "ecdsa-sha2-nistp521": "ssh-ecdsa"
261
+ };
262
+ var SNMP_AUTH = { sha1: "authentication-sha", sha256: "authentication-sha256" };
263
+ var SNMP_PRIVACY = { aes128: "privacy-aes128", des: "privacy-des" };
264
+ var SNMP_GROUP = "circuit";
265
+ var LEVEL = {
266
+ emergency: "emergency",
267
+ alert: "alert",
268
+ critical: "critical",
269
+ error: "error",
270
+ warning: "warning",
271
+ notice: "notice",
272
+ info: "info",
273
+ debug: "any"
274
+ };
275
+ var SERVICES_FILTER = { inet: "circuit-services", inet6: "circuit-services-v6" };
276
+ var OSPF_EXPORT = "circuit-ospf-redistribute";
277
+ var SSH_CIPHERS = ["aes256-gcm@openssh.com", "aes128-gcm@openssh.com", "aes256-ctr", "aes192-ctr", "aes128-ctr"];
278
+ var SSH_MACS = ["hmac-sha2-512", "hmac-sha2-256"];
279
+ var SSH_KEY_EXCHANGES = ["curve25519-sha256", "ecdh-sha2-nistp521", "ecdh-sha2-nistp384", "ecdh-sha2-nistp256", "group-exchange-sha2"];
280
+ var SERVICE_PORTS = {
281
+ ssh: ["tcp", 22],
282
+ telnet: ["tcp", 23],
283
+ ftp: ["tcp", 21],
284
+ snmp: ["udp", 161],
285
+ http: ["tcp", 80],
286
+ api: ["tcp", 3000],
287
+ native: ["tcp", 830]
288
+ };
289
+ var BFD_INTERVAL = 300;
290
+ function quote(value) {
291
+ if (/^[\w.:/@-]+$/.test(value))
292
+ return value;
293
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
294
+ return `"${escaped}"`;
295
+ }
296
+
297
+ class Statements {
298
+ lines = [];
299
+ notes = [];
300
+ set(...words) {
301
+ this.lines.push(`set ${words.join(" ")}`);
302
+ }
303
+ note(comment) {
304
+ this.notes.push(comment);
305
+ }
306
+ text() {
307
+ const unique = [...new Set(this.lines)];
308
+ const extended = new Set;
309
+ for (const line of unique) {
310
+ const words = line.split(" ");
311
+ for (let length = 2;length < words.length; length++)
312
+ extended.add(words.slice(0, length).join(" "));
313
+ }
314
+ return [...unique.filter((line) => !extended.has(line)), ...this.notes].join(`
315
+ `) + `
316
+ `;
317
+ }
318
+ }
319
+
320
+ class Names {
321
+ device;
322
+ lags;
323
+ loopbacks = new Map;
324
+ tunnels = new Map;
325
+ constructor(device) {
326
+ this.device = device;
327
+ this.lags = lagIds(device);
328
+ const loopbacks = Object.entries(device.interfaces ?? {}).filter(([, iface]) => iface.type === "loopback");
329
+ let unit = 0;
330
+ for (const [name, iface] of loopbacks)
331
+ if (!iface.vrf)
332
+ this.loopbacks.set(name, 0);
333
+ for (const [name, iface] of loopbacks)
334
+ if (iface.vrf)
335
+ this.loopbacks.set(name, ++unit);
336
+ const tunnels = Object.entries(device.interfaces ?? {}).filter(([, iface]) => iface.type === "gre");
337
+ tunnels.forEach(([name], index) => this.tunnels.set(name, index));
338
+ }
339
+ port(port) {
340
+ const name = spell(this.device.platform, this.device.model, port);
341
+ const settings = this.device.ports?.[port];
342
+ return settings?.speed === "1g" ? name.replace(/^xe-/, "ge-") : name;
343
+ }
344
+ physical(name) {
345
+ if (this.device.interfaces?.[name]?.type === "lag")
346
+ return `ae${this.lags.get(name)}`;
347
+ return this.port(name);
348
+ }
349
+ unit(name) {
350
+ const iface = this.device.interfaces?.[name];
351
+ if (iface?.type === "vlan")
352
+ return `irb.${this.device.vlans[iface.vlan].id}`;
353
+ if (iface?.type === "loopback")
354
+ return `lo0.${this.loopbacks.get(name)}`;
355
+ if (iface?.type === "gre")
356
+ return `gr-0/0/0.${this.tunnels.get(name)}`;
357
+ return `${this.physical(name)}.0`;
358
+ }
359
+ members(name) {
360
+ if (this.device.interfaces?.[name]?.type !== "lag")
361
+ return [this.port(name)];
362
+ const ports = Object.entries(this.device.ports ?? {}).filter(([, settings]) => settings?.lag === name);
363
+ return ports.map(([port]) => this.port(port));
364
+ }
365
+ }
366
+ function vrrpAddress(addresses, group) {
367
+ return addresses.find((address) => familyOf2(address) === "ipv4" && contains2(address, group.address));
368
+ }
369
+ var STORM_KINDS = [
370
+ ["broadcast", "no-broadcast"],
371
+ ["multicast", "no-multicast"],
372
+ ["unknown_unicast", "no-unknown-unicast"]
373
+ ];
374
+ function stormProfile(settings) {
375
+ const storm = settings.storm_control;
376
+ if (!storm)
377
+ return;
378
+ const limited = STORM_KINDS.filter(([kind]) => storm[kind]);
379
+ const first = limited[0] && storm[limited[0][0]];
380
+ if (!first || !("percent" in first))
381
+ return;
382
+ const kinds = limited.map(([kind]) => kind.replace("_", "-"));
383
+ const left = STORM_KINDS.filter(([kind]) => !storm[kind]).map(([, statement]) => statement);
384
+ return { name: `${kinds.join("-")}-${first.percent}-percent`, percent: first.percent, left };
385
+ }
386
+ function render(device) {
387
+ const out = new Statements;
388
+ const names = new Names(device);
389
+ system(out, device);
390
+ users(out, device);
391
+ services(out, device);
392
+ snmp(out, device);
393
+ vlans(out, device, names);
394
+ ports(out, device, names);
395
+ lags(out, device, names);
396
+ routedInterfaces(out, device, names);
397
+ stormProfiles(out, device);
398
+ servicesFilter(out, device, names);
399
+ vrfs(out, device, names);
400
+ staticRoutes(out, device, names);
401
+ spanningTree(out, device, names);
402
+ lldp(out, device, names);
403
+ ospf(out, device, names);
404
+ dhcpRelay(out, device, names);
405
+ dhcpServers(out, device, names);
406
+ routerAdvertisements(out, device, names);
407
+ acls(out, device, names);
408
+ renderBgp(out, device);
409
+ sflow(out, device, names);
410
+ return out.text();
411
+ }
412
+ function system(out, device) {
413
+ out.set("system host-name", quote(device.name));
414
+ const system = device.system ?? {};
415
+ if (system.timezone)
416
+ out.set("system time-zone", system.timezone);
417
+ if (system.banner)
418
+ out.set("system login message", quote(system.banner));
419
+ for (const server of system.dns?.servers ?? [])
420
+ out.set("system name-server", server);
421
+ for (const server of system.ntp?.servers ?? [])
422
+ out.set("system ntp server", server);
423
+ if (!system.ip?.icmp_redirects) {
424
+ out.set("system no-redirects");
425
+ out.set("system no-redirects-ipv6");
426
+ }
427
+ const local = system.logging?.local;
428
+ if (local) {
429
+ out.set("system syslog file messages any", LEVEL[local.level]);
430
+ if (local.files !== undefined)
431
+ out.set("system syslog file messages archive files", local.files);
432
+ }
433
+ for (const remote of system.logging?.remote ?? []) {
434
+ out.set("system syslog host", remote.address, "any", LEVEL[remote.level]);
435
+ if (remote.port !== undefined && remote.port !== 514)
436
+ out.set("system syslog host", remote.address, "port", remote.port);
437
+ }
438
+ }
439
+ function users(out, device) {
440
+ for (const [name, user] of Object.entries(device.users ?? {})) {
441
+ const login = name === "root" ? "system root-authentication" : `system login user ${name} authentication`;
442
+ if (name !== "root") {
443
+ out.set(`system login user ${name} class`, USER_CLASS[user.role]);
444
+ if (user.description)
445
+ out.set(`system login user ${name} full-name`, quote(user.description));
446
+ }
447
+ out.set(login, "plain-text-password-value", marker(user.password));
448
+ for (const key of user.ssh_keys ?? [])
449
+ out.set(login, KEY_STATEMENT[key.split(" ")[0]], quote(key));
450
+ }
451
+ }
452
+ function services(out, device) {
453
+ const management = device.management ?? {};
454
+ const ssh = management.ssh;
455
+ if (ssh) {
456
+ out.set("system services ssh");
457
+ if (ssh.port !== undefined && ssh.port !== 22)
458
+ out.set("system services ssh port", ssh.port);
459
+ if (!ssh.weak_crypto) {
460
+ for (const cipher of SSH_CIPHERS)
461
+ out.set("system services ssh ciphers", cipher);
462
+ for (const mac of SSH_MACS)
463
+ out.set("system services ssh macs", mac);
464
+ for (const exchange of SSH_KEY_EXCHANGES)
465
+ out.set("system services ssh key-exchange", exchange);
466
+ }
467
+ }
468
+ if (management.telnet)
469
+ out.set("system services telnet");
470
+ if (management.ftp)
471
+ out.set("system services ftp");
472
+ const others = [
473
+ ["http", "system services web-management http"],
474
+ ["api", "system services rest http"],
475
+ ["native", "system services netconf ssh"]
476
+ ];
477
+ for (const [service, statement] of others) {
478
+ const settings = management[service];
479
+ if (!settings)
480
+ continue;
481
+ out.set(statement);
482
+ if (settings.port !== undefined && settings.port !== SERVICE_PORTS[service][1])
483
+ out.set(statement, "port", settings.port);
484
+ }
485
+ }
486
+ function snmp(out, device) {
487
+ const snmp = device.management?.snmp;
488
+ if (!snmp)
489
+ return;
490
+ if (snmp.contact)
491
+ out.set("snmp contact", quote(snmp.contact));
492
+ if (snmp.location)
493
+ out.set("snmp location", quote(snmp.location));
494
+ if (snmp.community)
495
+ out.set("snmp community", marker(snmp.community), "authorization read-only");
496
+ if (!snmp.users)
497
+ return;
498
+ for (const [name, user] of Object.entries(snmp.users)) {
499
+ const usm = `snmp v3 usm local-engine user ${name}`;
500
+ out.set(usm, SNMP_AUTH[user.auth], "authentication-password", marker(user.auth_password));
501
+ out.set(usm, SNMP_PRIVACY[user.privacy], "privacy-password", marker(user.privacy_password));
502
+ out.set("snmp v3 vacm security-to-group security-model usm security-name", name, "group", SNMP_GROUP);
503
+ }
504
+ out.set("snmp view all oid .1 include");
505
+ out.set(`snmp v3 vacm access group ${SNMP_GROUP} default-context-prefix security-model usm security-level privacy read-view all`);
506
+ }
507
+ function vlans(out, device, names) {
508
+ for (const [name, vlan] of Object.entries(device.vlans ?? {})) {
509
+ out.set(`vlans ${name} vlan-id`, vlan.id);
510
+ if (vlan.description)
511
+ out.set(`vlans ${name} description`, quote(vlan.description));
512
+ }
513
+ for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
514
+ if (iface.type === "vlan")
515
+ out.set(`vlans ${iface.vlan} l3-interface`, names.unit(name));
516
+ }
517
+ }
518
+ function switching(out, device, physical, settings) {
519
+ const family = `interfaces ${physical} unit 0 family ethernet-switching`;
520
+ if (settings.access_vlan) {
521
+ out.set(family, "interface-mode access");
522
+ out.set(family, "vlan members", settings.access_vlan);
523
+ }
524
+ if (settings.trunk_vlans) {
525
+ out.set(family, "interface-mode trunk");
526
+ const members = new Set([...settings.trunk_vlans, ...settings.native_vlan ? [settings.native_vlan] : []]);
527
+ for (const vlan of members)
528
+ out.set(family, "vlan members", vlan);
529
+ if (settings.native_vlan)
530
+ out.set(`interfaces ${physical} native-vlan-id`, device.vlans[settings.native_vlan].id);
531
+ }
532
+ const storm = stormProfile(settings);
533
+ if (storm)
534
+ out.set(family, "storm-control", storm.name);
535
+ }
536
+ function addressing(out, unit, settings) {
537
+ const [physical, number] = unit.split(".");
538
+ const prefix = `interfaces ${physical} unit ${number}`;
539
+ const addresses = (settings.addresses ?? []).map((entry) => typeof entry === "string" ? entry : entry.address);
540
+ for (const address of addresses)
541
+ out.set(`${prefix} family ${familyOf2(address) === "ipv4" ? "inet" : "inet6"} address`, address);
542
+ for (const group of settings.vrrp ?? []) {
543
+ const address = vrrpAddress(addresses, group);
544
+ if (!address)
545
+ continue;
546
+ const statement = `${prefix} family inet address ${address} vrrp-group ${group.id}`;
547
+ out.set(statement, "virtual-address", group.address);
548
+ if (group.priority !== undefined)
549
+ out.set(statement, "priority", group.priority);
550
+ if (!group.preempt)
551
+ out.set(statement, "no-preempt");
552
+ if (group.interval !== undefined && group.interval !== 1)
553
+ out.set(statement, "advertise-interval", group.interval);
554
+ }
555
+ }
556
+ function ports(out, device, names) {
557
+ for (const port of portsOf(device.platform, device.model)) {
558
+ const settings = device.ports?.[port];
559
+ const name = names.port(port);
560
+ if (name.startsWith("xe-"))
561
+ out.set(`interfaces ${name.replace("xe-", "ge-")} disable`);
562
+ if (name.startsWith("ge-"))
563
+ out.set(`interfaces ${name.replace("ge-", "xe-")} disable`);
564
+ if (!settings) {
565
+ out.set(`interfaces ${name} disable`);
566
+ continue;
567
+ }
568
+ if (settings.description)
569
+ out.set(`interfaces ${name} description`, quote(settings.description));
570
+ if (settings.lag) {
571
+ out.set(`interfaces ${name} ether-options 802.3ad`, `ae${names.lags.get(settings.lag)}`);
572
+ continue;
573
+ }
574
+ const switched = !!(settings.access_vlan || settings.trunk_vlans);
575
+ if (settings.mtu !== undefined)
576
+ out.set(`interfaces ${name} mtu`, frameSize(settings.mtu, switched));
577
+ switching(out, device, name, settings);
578
+ addressing(out, `${name}.0`, settings);
579
+ }
580
+ }
581
+ function lags(out, device, names) {
582
+ let highest = -1;
583
+ for (const [name, lag] of Object.entries(device.interfaces ?? {})) {
584
+ if (lag.type !== "lag")
585
+ continue;
586
+ const physical = names.physical(name);
587
+ highest = Math.max(highest, names.lags.get(name));
588
+ if (lag.description)
589
+ out.set(`interfaces ${physical} description`, quote(lag.description));
590
+ if (lag.mode !== "static")
591
+ out.set(`interfaces ${physical} aggregated-ether-options lacp active`);
592
+ if (lag.mtu !== undefined)
593
+ out.set(`interfaces ${physical} mtu`, frameSize(lag.mtu, !!(lag.access_vlan || lag.trunk_vlans)));
594
+ switching(out, device, physical, lag);
595
+ addressing(out, `${physical}.0`, lag);
596
+ }
597
+ if (highest >= 0)
598
+ out.set("chassis aggregated-devices ethernet device-count", highest + 1);
599
+ }
600
+ function routedInterfaces(out, device, names) {
601
+ for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
602
+ if (iface.type !== "vlan" && iface.type !== "loopback" && iface.type !== "gre")
603
+ continue;
604
+ const unit = names.unit(name);
605
+ const [physical, number] = unit.split(".");
606
+ const prefix = `interfaces ${physical} unit ${number}`;
607
+ if (iface.description)
608
+ out.set(`${prefix} description`, quote(iface.description));
609
+ if (iface.type === "gre") {
610
+ out.set(`${prefix} tunnel source`, iface.local);
611
+ out.set(`${prefix} tunnel destination`, iface.remote);
612
+ }
613
+ addressing(out, unit, iface);
614
+ if (iface.mtu === undefined)
615
+ continue;
616
+ const addresses = (iface.addresses ?? []).map((entry) => typeof entry === "string" ? entry : entry.address);
617
+ const families = new Set(addresses.map((address) => familyOf2(address) === "ipv4" ? "inet" : "inet6"));
618
+ for (const family of families)
619
+ out.set(`${prefix} family ${family} mtu`, iface.mtu);
620
+ }
621
+ }
622
+ function stormProfiles(out, device) {
623
+ const switched = [
624
+ ...Object.values(device.ports ?? {}).filter((settings) => !!settings && !settings.lag),
625
+ ...Object.values(device.interfaces ?? {}).filter((iface) => iface.type === "lag")
626
+ ];
627
+ for (const settings of switched) {
628
+ const storm = stormProfile(settings);
629
+ if (!storm)
630
+ continue;
631
+ const profile = `forwarding-options storm-control-profiles ${storm.name} all`;
632
+ out.set(profile, "bandwidth-percentage", storm.percent);
633
+ for (const statement of storm.left)
634
+ out.set(profile, statement);
635
+ }
636
+ }
637
+ function servicesFilter(out, device, names) {
638
+ const management = device.management ?? {};
639
+ const guarded = [];
640
+ for (const service of Object.keys(SERVICE_PORTS)) {
641
+ const settings = management[service];
642
+ const sources = settings?.allow ?? management.allow;
643
+ if (settings && sources?.length)
644
+ guarded.push({ service, sources });
645
+ }
646
+ const ntp = device.system?.ntp;
647
+ const keepsTimeQuietly = !!ntp?.servers.length && !ntp.serve;
648
+ if (!guarded.length && !keepsTimeQuietly)
649
+ return;
650
+ const families = [
651
+ ["inet", "ipv4", "protocol"],
652
+ ["inet6", "ipv6", "next-header"]
653
+ ];
654
+ for (const [family, addressFamily, protocolWord] of families) {
655
+ const filter = `firewall family ${family} filter ${SERVICES_FILTER[family]}`;
656
+ const ofFamily = (prefix) => familyOf2(host2(prefix)) === addressFamily;
657
+ for (const { service, sources } of guarded) {
658
+ const [protocol, standardPort] = SERVICE_PORTS[service];
659
+ const port = management[service]?.port ?? standardPort;
660
+ const term = `${filter} term ${service}`;
661
+ for (const source of sources.filter(ofFamily))
662
+ out.set(term, "from source-address", source);
663
+ out.set(term, "from", protocolWord, protocol);
664
+ out.set(term, "from destination-port", port);
665
+ out.set(term, "then accept");
666
+ const others = `${filter} term ${service}-others`;
667
+ out.set(others, "from", protocolWord, protocol);
668
+ out.set(others, "from destination-port", port);
669
+ out.set(others, "then discard");
670
+ }
671
+ if (keepsTimeQuietly) {
672
+ const servers = ntp.servers.filter(ofFamily);
673
+ if (servers.length) {
674
+ const length = addressFamily === "ipv4" ? 32 : 128;
675
+ for (const server of servers)
676
+ out.set(`${filter} term ntp from source-address`, `${server}/${length}`);
677
+ out.set(`${filter} term ntp from`, protocolWord, "udp");
678
+ out.set(`${filter} term ntp from destination-port 123`);
679
+ out.set(`${filter} term ntp then accept`);
680
+ }
681
+ out.set(`${filter} term ntp-others from`, protocolWord, "udp");
682
+ out.set(`${filter} term ntp-others from destination-port 123`);
683
+ out.set(`${filter} term ntp-others then discard`);
684
+ }
685
+ out.set(`${filter} term everything-else then accept`);
686
+ }
687
+ const anchors = new Set(["lo0.0", ...[...names.loopbacks.values()].map((unit) => `lo0.${unit}`)]);
688
+ for (const [port, settings] of Object.entries(device.ports ?? {})) {
689
+ const name = names.port(port);
690
+ if (settings?.addresses?.length && name.startsWith("em"))
691
+ anchors.add(`${name}.0`);
692
+ }
693
+ for (const anchor of anchors) {
694
+ const [physical, unit] = anchor.split(".");
695
+ out.set(`interfaces ${physical} unit ${unit} family inet filter input ${SERVICES_FILTER.inet}`);
696
+ out.set(`interfaces ${physical} unit ${unit} family inet6 filter input ${SERVICES_FILTER.inet6}`);
697
+ }
698
+ }
699
+ function vrfs(out, device, names) {
700
+ for (const [name, vrf] of Object.entries(device.vrfs ?? {})) {
701
+ out.set(`routing-instances ${name} instance-type virtual-router`);
702
+ if (vrf.description)
703
+ out.set(`routing-instances ${name} description`, quote(vrf.description));
704
+ }
705
+ const routed = [...Object.entries(device.ports ?? {}), ...Object.entries(device.interfaces ?? {})];
706
+ for (const [name, settings] of routed) {
707
+ if (settings?.vrf)
708
+ out.set(`routing-instances ${settings.vrf} interface`, names.unit(name));
709
+ }
710
+ }
711
+ function staticRoutes(out, device, names) {
712
+ if (device.routing?.router_id)
713
+ out.set("routing-options router-id", device.routing.router_id);
714
+ for (const route of device.routing?.static ?? []) {
715
+ const ipv6 = familyOf2(split(route.prefix).addr) === "ipv6";
716
+ const table = ipv6 ? ` rib ${route.vrf ? `${route.vrf}.` : ""}inet6.0` : "";
717
+ const options = route.vrf ? `routing-instances ${route.vrf} routing-options${table}` : `routing-options${table}`;
718
+ const statement = `${options} static route ${route.prefix}`;
719
+ if (route.blackhole) {
720
+ out.set(statement, "discard");
721
+ if (route.distance !== undefined)
722
+ out.set(statement, "preference", route.distance);
723
+ continue;
724
+ }
725
+ const nextHop = route.via ?? names.unit(route.interface);
726
+ if (route.distance === undefined)
727
+ out.set(statement, "next-hop", nextHop);
728
+ else
729
+ out.set(statement, "qualified-next-hop", nextHop, "preference", route.distance);
730
+ }
731
+ }
732
+ function spanningTree(out, device, names) {
733
+ const stp = device.stp;
734
+ if (!stp)
735
+ return;
736
+ const protocol = `protocols ${stp.mode === "mstp" ? "mstp" : "rstp"}`;
737
+ if (stp.mode === "stp")
738
+ out.set(protocol, "force-version stp");
739
+ if (stp.priority !== undefined)
740
+ out.set(protocol, "bridge-priority", stp.priority === 0 ? "0" : `${stp.priority / 1024}k`);
741
+ const switched = [];
742
+ for (const [port, settings] of Object.entries(device.ports ?? {})) {
743
+ if (settings && !settings.lag && (settings.access_vlan || settings.trunk_vlans))
744
+ switched.push([names.port(port), settings]);
745
+ }
746
+ for (const [name, lag] of Object.entries(device.interfaces ?? {})) {
747
+ if (lag.type === "lag" && (lag.access_vlan || lag.trunk_vlans))
748
+ switched.push([names.physical(name), lag]);
749
+ }
750
+ for (const [name, settings] of switched) {
751
+ const statement = `${protocol} interface ${name}`;
752
+ out.set(statement);
753
+ if (settings.stp === false)
754
+ out.set(statement, "disable");
755
+ else if (typeof settings.stp === "object") {
756
+ if (settings.stp.edge)
757
+ out.set(statement, "edge");
758
+ if (settings.stp.cost !== undefined)
759
+ out.set(statement, "cost", settings.stp.cost);
760
+ if (settings.stp.priority !== undefined)
761
+ out.set(statement, "priority", settings.stp.priority);
762
+ }
763
+ }
764
+ }
765
+ function lldp(out, device, names) {
766
+ const lldp = device.lldp;
767
+ if (lldp === true)
768
+ out.set("protocols lldp interface all");
769
+ else if (lldp)
770
+ for (const name of lldp.interfaces)
771
+ for (const port of names.members(name))
772
+ out.set("protocols lldp interface", port);
773
+ for (const [port, settings] of Object.entries(device.ports ?? {})) {
774
+ if (settings?.lldp === false)
775
+ out.set("protocols lldp interface", names.port(port), "disable");
776
+ else if (settings?.lldp === true)
777
+ out.set("protocols lldp interface", names.port(port));
778
+ }
779
+ }
780
+ function ospf(out, device, names) {
781
+ const ospf = device.routing?.ospf;
782
+ if (!ospf)
783
+ return;
784
+ const protocols = (ospf.families ?? ["ipv4"]).map((family) => family === "ipv4" ? "ospf" : "ospf3");
785
+ for (const protocol of protocols) {
786
+ for (const [areaId, area] of Object.entries(ospf.areas)) {
787
+ for (const [iface, settings] of Object.entries(area.interfaces)) {
788
+ const statement = `protocols ${protocol} area ${areaId} interface ${names.unit(iface)}`;
789
+ out.set(statement);
790
+ if (settings?.cost !== undefined)
791
+ out.set(statement, "metric", settings.cost);
792
+ if (settings?.passive)
793
+ out.set(statement, "passive");
794
+ if (settings?.network === "point-to-point")
795
+ out.set(statement, "interface-type p2p");
796
+ if (settings?.bfd)
797
+ out.set(statement, "bfd-liveness-detection minimum-interval", BFD_INTERVAL);
798
+ }
799
+ }
800
+ if (ospf.redistribute?.length)
801
+ out.set(`protocols ${protocol} export`, OSPF_EXPORT);
802
+ }
803
+ for (const source of ospf.redistribute ?? []) {
804
+ const term = `policy-options policy-statement ${OSPF_EXPORT} term ${source}`;
805
+ out.set(term, "from protocol", source === "connected" ? "direct" : "static");
806
+ out.set(term, "then accept");
807
+ }
808
+ }
809
+ function dhcpRelay(out, device, names) {
810
+ for (const [name, relay] of Object.entries(device.dhcp_relay ?? {})) {
811
+ const vrf = device.interfaces?.[relay.interface]?.vrf ?? device.ports?.[relay.interface]?.vrf;
812
+ const relays = vrf ? `routing-instances ${vrf} forwarding-options dhcp-relay` : "forwarding-options dhcp-relay";
813
+ for (const server of relay.servers)
814
+ out.set(`${relays} server-group ${name}`, server);
815
+ out.set(`${relays} group ${name} active-server-group`, name);
816
+ out.set(`${relays} group ${name} interface`, names.unit(relay.interface));
817
+ }
818
+ }
819
+ function sflow(out, device, names) {
820
+ const flow = device.flow_export;
821
+ if (flow?.protocol !== "sflow")
822
+ return;
823
+ out.set("protocols sflow agent-id", device.connection.host);
824
+ if (flow.sampling !== undefined)
825
+ out.set("protocols sflow sample-rate ingress", flow.sampling);
826
+ for (const collector of flow.collectors) {
827
+ out.set("protocols sflow collector", collector.address);
828
+ if (collector.port !== undefined && collector.port !== 6343) {
829
+ out.set("protocols sflow collector", collector.address, "udp-port", collector.port);
830
+ }
831
+ }
832
+ const declared = Object.entries(device.ports ?? {}).filter(([, settings]) => settings && !settings.lag);
833
+ const observed = flow.interfaces ?? declared.map(([port]) => port);
834
+ for (const name of observed) {
835
+ for (const port of names.members(name))
836
+ if (!port.startsWith("em"))
837
+ out.set("protocols sflow interfaces", `${port}.0`);
838
+ }
839
+ }
840
+ var frameSize = (mtu, switched) => mtu + (switched ? 18 : 14);
841
+ var bandwidth = (rate) => rate.toLowerCase();
842
+ function burstOf(rate) {
843
+ const scale = { k: 1000, m: 1e6, g: 1e9 }[rate.slice(-1).toLowerCase()];
844
+ return Math.max(1500, Math.round(Number(rate.slice(0, -1)) * scale / 800));
845
+ }
846
+ var eachPort = (ports) => [ports].flat().map((port) => typeof port === "number" ? String(port) : `${port.min}-${port.max}`);
847
+ function aclFamily(rule) {
848
+ const match = rule.match;
849
+ if (match?.family)
850
+ return match.family;
851
+ const address = match?.src ?? match?.dst;
852
+ return address ? familyOf2(host2(address)) : undefined;
853
+ }
854
+ var nextHeader = (protocol) => protocol === "icmp" ? "icmp6" : protocol;
855
+ var ruleName = (label, description) => quote(description ? `${label} ${description}` : label);
856
+ function acls(out, device, names) {
857
+ const switched = new Set;
858
+ const routed = new Set;
859
+ for (const [port, settings] of Object.entries(device.ports ?? {})) {
860
+ if (!settings?.acl)
861
+ continue;
862
+ const physical = names.port(port);
863
+ if (settings.access_vlan || settings.trunk_vlans) {
864
+ switched.add(settings.acl);
865
+ out.set(`interfaces ${physical} unit 0 family ethernet-switching filter input`, settings.acl);
866
+ } else {
867
+ routed.add(settings.acl);
868
+ out.set(`interfaces ${physical} unit 0 family inet filter input`, `${settings.acl}-v4`);
869
+ out.set(`interfaces ${physical} unit 0 family inet6 filter input`, `${settings.acl}-v6`);
870
+ }
871
+ }
872
+ for (const acl of switched)
873
+ switchingFilter(out, device, acl);
874
+ for (const acl of routed) {
875
+ routedFilter(out, device, acl, "ipv4");
876
+ routedFilter(out, device, acl, "ipv6");
877
+ }
878
+ }
879
+ function aclThen(out, term, policer, rule) {
880
+ if (rule.action === "drop") {
881
+ out.set(term, "then discard");
882
+ return;
883
+ }
884
+ if (rule.rate) {
885
+ out.set(`firewall policer ${policer} if-exceeding bandwidth-limit`, bandwidth(rule.rate));
886
+ out.set(`firewall policer ${policer} if-exceeding burst-size-limit`, burstOf(rule.rate));
887
+ out.set(`firewall policer ${policer} then discard`);
888
+ out.set(term, "then policer", policer);
889
+ }
890
+ out.set(term, "then accept");
891
+ }
892
+ function switchingFilter(out, device, acl) {
893
+ const filter = `firewall family ethernet-switching filter ${acl}`;
894
+ (device.acls?.[acl] ?? []).forEach((rule, index) => {
895
+ const match = rule.match ?? {};
896
+ const addressed = match.src || match.dst || match.protocol || match.src_port !== undefined || match.dst_port !== undefined;
897
+ const family = aclFamily(rule);
898
+ const families = addressed || family ? family ? [family] : ["ipv4", "ipv6"] : [undefined];
899
+ for (const each of families) {
900
+ const label = families.length > 1 ? `${index + 1}-${each === "ipv4" ? "v4" : "v6"}` : `${index + 1}`;
901
+ const term = `${filter} term ${ruleName(label, rule.description)}`;
902
+ if (each) {
903
+ const version = `from ip-version ${each}`;
904
+ const v4 = each === "ipv4";
905
+ if (match.src)
906
+ out.set(term, version, v4 ? "ip-source-address" : "ip6-source-address", match.src);
907
+ if (match.dst)
908
+ out.set(term, version, v4 ? "ip-destination-address" : "ip6-destination-address", match.dst);
909
+ if (match.protocol)
910
+ out.set(term, version, v4 ? "ip-protocol" : "next-header", v4 ? match.protocol : nextHeader(match.protocol));
911
+ for (const port of match.src_port !== undefined ? eachPort(match.src_port) : [])
912
+ out.set(term, version, "source-port", port);
913
+ for (const port of match.dst_port !== undefined ? eachPort(match.dst_port) : [])
914
+ out.set(term, version, "destination-port", port);
915
+ if (!addressed)
916
+ out.set(term, version);
917
+ }
918
+ aclThen(out, term, `${acl}-${label}`, rule);
919
+ }
920
+ });
921
+ out.set(filter, "term circuit-default then accept");
922
+ }
923
+ function routedFilter(out, device, acl, family) {
924
+ const name = `${acl}-${family === "ipv4" ? "v4" : "v6"}`;
925
+ const filter = `firewall family ${family === "ipv4" ? "inet" : "inet6"} filter ${name}`;
926
+ (device.acls?.[acl] ?? []).forEach((rule, index) => {
927
+ const ruleFamily = aclFamily(rule);
928
+ if (ruleFamily && ruleFamily !== family)
929
+ return;
930
+ const match = rule.match ?? {};
931
+ const label = `${index + 1}`;
932
+ const term = `${filter} term ${ruleName(label, rule.description)}`;
933
+ const protocolWord = family === "ipv4" ? "protocol" : "next-header";
934
+ if (match.src)
935
+ out.set(term, "from source-address", match.src);
936
+ if (match.dst)
937
+ out.set(term, "from destination-address", match.dst);
938
+ if (match.protocol)
939
+ out.set(term, "from", protocolWord, family === "ipv4" ? match.protocol : nextHeader(match.protocol));
940
+ for (const port of match.src_port !== undefined ? eachPort(match.src_port) : [])
941
+ out.set(term, "from source-port", port);
942
+ for (const port of match.dst_port !== undefined ? eachPort(match.dst_port) : [])
943
+ out.set(term, "from destination-port", port);
944
+ aclThen(out, term, `${name}-${label}`, rule);
945
+ });
946
+ out.set(filter, "term circuit-default then accept");
947
+ }
948
+ function dhcpServers(out, device, names) {
949
+ for (const [name, server] of Object.entries(device.dhcp ?? {})) {
950
+ out.set(`system services dhcp-local-server group ${name} interface`, names.unit(server.interface));
951
+ const pool = `access address-assignment pool ${name} family inet`;
952
+ const [low, high] = server.pool;
953
+ out.set(pool, "network", server.network);
954
+ out.set(pool, `range ${name} low`, low);
955
+ out.set(pool, `range ${name} high`, high);
956
+ out.set(pool, "dhcp-attributes router", server.gateway);
957
+ for (const resolver of server.dns ?? [])
958
+ out.set(pool, "dhcp-attributes name-server", resolver);
959
+ if (server.lease_time !== undefined)
960
+ out.set(pool, "dhcp-attributes maximum-lease-time", server.lease_time);
961
+ for (const reservation of server.reservations ?? []) {
962
+ const host = `${pool} host ${reservation.address.replace(/\./g, "-")}`;
963
+ out.set(host, "hardware-address", reservation.mac.toLowerCase());
964
+ out.set(host, "ip-address", reservation.address);
965
+ }
966
+ }
967
+ }
968
+ function routerAdvertisements(out, device, names) {
969
+ const routed = [...Object.entries(device.ports ?? {}), ...Object.entries(device.interfaces ?? {})];
970
+ for (const [name, settings] of routed) {
971
+ if (!settings?.ipv6_ra)
972
+ continue;
973
+ const addresses = (settings.addresses ?? []).map((entry) => typeof entry === "string" ? entry : entry.address);
974
+ for (const address of addresses.filter((address) => familyOf2(address) === "ipv6")) {
975
+ out.set(`protocols router-advertisement interface ${names.unit(name)} prefix`, network(address));
976
+ }
977
+ }
978
+ }
979
+
980
+ // src/adapters/devices/junos/bgp.ts
981
+ var REJECT_ALL = "circuit-reject-all";
982
+ var ORIGIN = "circuit-bgp-origin";
983
+ var RPKI_GROUP = "circuit";
984
+ var GENERATED = "# generated ";
985
+ var FAMILY = { ipv4: "inet", ipv6: "inet6" };
986
+ var VALIDATION = { valid: "valid", invalid: "invalid", "not-found": "unknown" };
987
+ function settingsOf(device, neighbor) {
988
+ const group = neighbor.group ? device.routing?.bgp?.groups?.[neighbor.group] : undefined;
989
+ return { ...group, ...neighbor };
990
+ }
991
+ var slug = (value) => value.replace(/\*/g, "any").replace(/[^A-Za-z0-9]+/g, "-");
992
+ var member = (community, large) => large ? `large:${community}` : community;
993
+ function renderBgp(out, device) {
994
+ prefixSets(out, device);
995
+ rpki(out, device);
996
+ const bgp = device.routing?.bgp;
997
+ if (!bgp)
998
+ return;
999
+ out.set("routing-options autonomous-system", bgp.asn);
1000
+ const used = new Set;
1001
+ let rejectAll = false;
1002
+ let exports = false;
1003
+ for (const [name, neighbor] of Object.entries(bgp.neighbors ?? {})) {
1004
+ const settings = settingsOf(device, neighbor);
1005
+ const group = `protocols bgp group ${name}`;
1006
+ out.set(group, "type", settings.remote_as === bgp.asn ? "internal" : "external");
1007
+ out.set(group, "peer-as", settings.remote_as);
1008
+ if (settings.description)
1009
+ out.set(group, "description", quote(settings.description));
1010
+ if (settings.local_address)
1011
+ out.set(group, "local-address", settings.local_address);
1012
+ if (settings.multihop)
1013
+ out.set(group, "multihop");
1014
+ if (settings.passive)
1015
+ out.set(group, "passive");
1016
+ if (settings.hold_time !== undefined)
1017
+ out.set(group, "hold-time", settings.hold_time);
1018
+ if (settings.password)
1019
+ out.set(group, "authentication-key", marker(settings.password));
1020
+ if (settings.bfd)
1021
+ out.set(group, "bfd-liveness-detection minimum-interval", BFD_INTERVAL);
1022
+ out.set(group, "import", settings.import ?? REJECT_ALL);
1023
+ if (settings.export) {
1024
+ exports = true;
1025
+ out.set(group, "export", ORIGIN);
1026
+ out.set(group, "export", settings.export);
1027
+ } else
1028
+ out.set(group, "export", REJECT_ALL);
1029
+ for (const policy of [settings.import, settings.export]) {
1030
+ if (policy)
1031
+ used.add(policy);
1032
+ else
1033
+ rejectAll = true;
1034
+ }
1035
+ for (const family of settings.families ?? [familyOf2(neighbor.address)]) {
1036
+ const nlri = `${group} family ${FAMILY[family]} unicast`;
1037
+ out.set(nlri);
1038
+ if (settings.max_prefixes !== undefined) {
1039
+ out.set(nlri, "prefix-limit maximum", settings.max_prefixes);
1040
+ out.set(nlri, "prefix-limit teardown idle-timeout forever");
1041
+ }
1042
+ }
1043
+ out.set(group, "neighbor", neighbor.address);
1044
+ }
1045
+ if (rejectAll)
1046
+ out.set(`policy-options policy-statement ${REJECT_ALL} then reject`);
1047
+ if (exports)
1048
+ origin(out, device);
1049
+ for (const policy of [...used].sort())
1050
+ policyStatement(out, device, policy);
1051
+ }
1052
+ function origin(out, device) {
1053
+ const statement = `policy-options policy-statement ${ORIGIN}`;
1054
+ out.set(statement, "term bgp from protocol bgp");
1055
+ out.set(statement, "term bgp then next policy");
1056
+ const networks = device.routing?.bgp?.networks ?? [];
1057
+ for (const prefix of networks)
1058
+ out.set(statement, "term networks from route-filter", prefix, "exact");
1059
+ if (networks.length)
1060
+ out.set(statement, "term networks then next policy");
1061
+ out.set(statement, "term others then reject");
1062
+ }
1063
+ function prefixSets(out, device) {
1064
+ for (const [name, set] of Object.entries(device.prefix_sets ?? {})) {
1065
+ const list = `policy-options prefix-list ${name}`;
1066
+ if ("prefixes" in set && set.prefixes.length)
1067
+ for (const prefix of set.prefixes)
1068
+ out.set(list, prefix);
1069
+ else
1070
+ out.set(list);
1071
+ if ("source" in set)
1072
+ out.note(`${GENERATED}${list}`);
1073
+ }
1074
+ }
1075
+ function rpki(out, device) {
1076
+ for (const server of device.routing?.rpki ?? []) {
1077
+ out.set(`routing-options validation group ${RPKI_GROUP} session`, server.address, "port", server.port);
1078
+ }
1079
+ }
1080
+ function familyOfRule(device, rule) {
1081
+ const match = rule.match;
1082
+ if (match?.family)
1083
+ return match.family;
1084
+ if (match?.prefix)
1085
+ return familyOf2(host2(match.prefix));
1086
+ const set = match?.prefix_set ? device.prefix_sets?.[match.prefix_set] : undefined;
1087
+ if (set && "source" in set)
1088
+ return set.source.family;
1089
+ if (set?.prefixes[0])
1090
+ return familyOf2(host2(set.prefixes[0]));
1091
+ return;
1092
+ }
1093
+ function flatten(device, policy, label, seen) {
1094
+ const rules = device.policies?.[policy] ?? [];
1095
+ const flat = [];
1096
+ rules.forEach((rule, index) => {
1097
+ const here = label ? `${label}.${index + 1}` : `${index + 1}`;
1098
+ if (rule.call && !seen.includes(rule.call))
1099
+ flat.push(...flatten(device, rule.call, here, [...seen, rule.call]));
1100
+ if (rule.match || rule.set || rule.action)
1101
+ flat.push({ label: here, rule });
1102
+ });
1103
+ return flat;
1104
+ }
1105
+ var termName = (label, rule) => quote(rule.description ? `${label} ${rule.description}` : label);
1106
+ function policyStatement(out, device, policy) {
1107
+ const statement = `policy-options policy-statement ${policy}`;
1108
+ const flat = flatten(device, policy, "", [policy]);
1109
+ for (const { label, rule } of flat) {
1110
+ const family = familyOfRule(device, rule);
1111
+ const split = !family && (rule.set?.next_hop || rule.match?.prefix_length) ? ["ipv4", "ipv6"] : [family];
1112
+ for (const part of split) {
1113
+ const name = split.length > 1 ? termName(`${label}-${part === "ipv4" ? "v4" : "v6"}`, rule) : termName(label, rule);
1114
+ term(out, device, `${statement} term ${name}`, rule, part, split.length > 1);
1115
+ }
1116
+ }
1117
+ const last = flat.at(-1)?.rule;
1118
+ const decides = last !== undefined && !last.match && last.action !== undefined;
1119
+ if (!decides)
1120
+ out.set(statement, "term circuit-default then reject");
1121
+ }
1122
+ function term(out, device, term, rule, family, splitFamily) {
1123
+ const match = rule.match ?? {};
1124
+ const set = rule.set ?? {};
1125
+ const from = [];
1126
+ const then = [];
1127
+ if (match.family || splitFamily)
1128
+ from.push(["family", FAMILY[match.family ?? family]]);
1129
+ if (match.prefix)
1130
+ from.push(["route-filter", match.prefix, "exact"]);
1131
+ if (match.prefix_set)
1132
+ from.push(["prefix-list-filter", match.prefix_set, "orlonger"]);
1133
+ if (match.prefix_length) {
1134
+ const families = family ? [family] : ["ipv4", "ipv6"];
1135
+ for (const each of families) {
1136
+ const longest = each === "ipv4" ? 32 : 128;
1137
+ const range = `/${match.prefix_length.min ?? 0}-/${match.prefix_length.max ?? longest}`;
1138
+ from.push(["route-filter", each === "ipv4" ? "0.0.0.0/0" : "::/0", "prefix-length-range", range]);
1139
+ }
1140
+ }
1141
+ if (match.as_path_length) {
1142
+ const { min = 0, max } = match.as_path_length;
1143
+ const name = `circuit-path-length-${min}-${max ?? "any"}`;
1144
+ out.set(`policy-options as-path ${name}`, quote(`.{${min},${max ?? ""}}`));
1145
+ from.push(["as-path", name]);
1146
+ }
1147
+ if (match.rpki)
1148
+ from.push(["validation-database", VALIDATION[match.rpki]]);
1149
+ const wanted = [
1150
+ ...match.community ? [member(match.community, false)] : [],
1151
+ ...match.large_community ? [member(match.large_community, true)] : []
1152
+ ];
1153
+ if (wanted.length)
1154
+ from.push(["community", community(out, "match", wanted)]);
1155
+ if (set.local_pref !== undefined)
1156
+ then.push(["local-preference", set.local_pref]);
1157
+ if (set.med !== undefined)
1158
+ then.push(["metric", set.med]);
1159
+ if (set.prepend !== undefined) {
1160
+ const asn = device.routing.bgp.asn;
1161
+ then.push(["as-path-prepend", quote(Array(set.prepend).fill(asn).join(" "))]);
1162
+ }
1163
+ const removed = [
1164
+ ...(set.communities ? ["*:*"] : set.remove_communities ?? []).map((pattern) => member(pattern, false)),
1165
+ ...(set.large_communities ? ["*:*:*"] : set.remove_large_communities ?? []).map((pattern) => member(pattern, true))
1166
+ ];
1167
+ const added = [
1168
+ ...(set.communities ?? set.add_communities ?? []).map((value) => member(value, false)),
1169
+ ...(set.large_communities ?? set.add_large_communities ?? []).map((value) => member(value, true))
1170
+ ];
1171
+ if (set.communities && set.add_communities)
1172
+ added.push(...set.add_communities.map((value) => member(value, false)));
1173
+ if (set.large_communities && set.add_large_communities)
1174
+ added.push(...set.add_large_communities.map((value) => member(value, true)));
1175
+ for (const pattern of removed)
1176
+ then.push(["community delete", community(out, "remove", [pattern])]);
1177
+ if (added.length)
1178
+ then.push(["community add", community(out, "add", added)]);
1179
+ if (set.next_hop && (!family || familyOf2(set.next_hop) === family))
1180
+ then.push(["next-hop", set.next_hop]);
1181
+ if (set.blackhole)
1182
+ then.push(["next-hop discard"]);
1183
+ if (rule.action)
1184
+ then.push([rule.action]);
1185
+ for (const words of from)
1186
+ out.set(term, "from", ...words);
1187
+ for (const words of then)
1188
+ out.set(term, "then", ...words);
1189
+ if (!from.length && !then.length)
1190
+ out.set(term);
1191
+ }
1192
+ function community(out, purpose, members) {
1193
+ const name = `circuit-${purpose}-${slug(members.join(" "))}`;
1194
+ for (const value of members)
1195
+ out.set(`policy-options community ${name} members`, quote(value));
1196
+ return name;
1197
+ }
1198
+
1199
+ // src/adapters/devices/junos/index.ts
1200
+ var SUPPORTED_MAJOR = 21;
1201
+ var DEVICE_FACTS = [/^set version /, /^set system login user \S+ uid \d+$/];
1202
+ var SYSTEM_ACCOUNTS = new Set(["bind", "daemon", "kmem", "nobody", "operator", "sshd", "tty", "uucp"]);
1203
+ function wordsOf(statement) {
1204
+ return statement.match(/"(?:[^"\\]|\\.)*"|\S+/g) ?? [];
1205
+ }
1206
+ var bare = (word) => word.startsWith('"') ? word.slice(1, -1).replace(/\\(.)/g, "$1") : word;
1207
+ var SECRETS = [
1208
+ [/ (?:encrypted-password|plain-text-password-value) (?:"[^"]*"|\S+)/, " password <secret>"],
1209
+ [/ (?:authentication-key|authentication-password) (?:"[^"]*"|\S+)/, " authentication-key <secret>"],
1210
+ [/ (?:privacy-key|privacy-password) (?:"[^"]*"|\S+)/, " privacy-key <secret>"],
1211
+ [/^set snmp community (?:"[^"]*"|\S+)/, "set snmp community <secret>"]
1212
+ ];
1213
+ function keyOf2(statement) {
1214
+ let canonical = statement;
1215
+ for (const [pattern, replacement] of SECRETS)
1216
+ canonical = canonical.replace(pattern, replacement);
1217
+ return wordsOf(canonical).slice(1).map(bare);
1218
+ }
1219
+ function scrub(text) {
1220
+ return text.replace(/ ## SECRET-DATA$/gm, "").replace(/((?:encrypted-password|authentication-key|privacy-key) )(?:"[^"]*"|\S+)/g, "$1<redacted>").replace(/^(set snmp community )(?:"[^"]*"|\S+)/gm, "$1<redacted>").replace(/"\$\d\$[^"]*"/g, "<redacted>");
1221
+ }
1222
+ function clean(output) {
1223
+ const statements = output.replace(/\r/g, "").split(`
1224
+ `).map((line) => line.trimEnd()).filter((line) => /^(set|deactivate) /.test(line)).filter((line) => !DEVICE_FACTS.some((fact) => fact.test(line)));
1225
+ return scrub(statements.join(`
1226
+ `) + `
1227
+ `);
1228
+ }
1229
+ var NAMED_LISTS = new Set([
1230
+ "address",
1231
+ "area",
1232
+ "as-path",
1233
+ "class",
1234
+ "collector",
1235
+ "community",
1236
+ "family",
1237
+ "file",
1238
+ "filter",
1239
+ "group",
1240
+ "host",
1241
+ "interface",
1242
+ "interfaces",
1243
+ "neighbor",
1244
+ "oid",
1245
+ "policer",
1246
+ "policy-statement",
1247
+ "pool",
1248
+ "prefix",
1249
+ "prefix-list",
1250
+ "qualified-next-hop",
1251
+ "range",
1252
+ "rib",
1253
+ "route",
1254
+ "security-level",
1255
+ "security-model",
1256
+ "security-name",
1257
+ "server",
1258
+ "server-group",
1259
+ "session",
1260
+ "storm-control-profiles",
1261
+ "term",
1262
+ "trap-group",
1263
+ "unit",
1264
+ "user",
1265
+ "view",
1266
+ "vlan",
1267
+ "vrrp-group"
1268
+ ]);
1269
+ function canStopAt(path, index) {
1270
+ if (index === 0 || !NAMED_LISTS.has(path[index]))
1271
+ return true;
1272
+ return path[index] === "vlan" && path[index + 1] === "members";
1273
+ }
1274
+ var sectionOf = (path) => path.slice(0, 2).join(" ");
1275
+ function orderedBlockOf(path) {
1276
+ if (path[0] === "policy-options" && path[1] === "policy-statement" && path[3] === "term") {
1277
+ return { block: path.slice(0, 3), item: path[4] };
1278
+ }
1279
+ if (path[0] === "firewall" && path[1] === "family" && path[3] === "filter" && path[5] === "term") {
1280
+ return { block: path.slice(0, 5), item: path[6] };
1281
+ }
1282
+ const chain = path.length - 2;
1283
+ if (path[0] === "protocols" && (path[chain] === "import" || path[chain] === "export")) {
1284
+ return { block: path.slice(0, chain + 1), item: path[chain + 1] };
1285
+ }
1286
+ return;
1287
+ }
1288
+ var startsWith = (path, prefix) => prefix.every((word, index) => path[index] === word);
1289
+ function plan(desired, current, options) {
1290
+ const lines = (text, verb) => text.split(`
1291
+ `).filter((line) => line.startsWith(`${verb} `));
1292
+ const wanted = lines(desired, "set");
1293
+ const deactivated = lines(current, "deactivate");
1294
+ const generated = desired.split(`
1295
+ `).filter((line) => line.startsWith(GENERATED)).map((line) => wordsOf(line.slice(GENERATED.length)).map(bare));
1296
+ const printed = lines(current, "set").filter((line) => !DEVICE_FACTS.some((fact) => fact.test(line)));
1297
+ const isGenerated = (line) => generated.some((path) => keyOf2(line).length > path.length && startsWith(keyOf2(line), path));
1298
+ const existing = printed.filter((line) => !isGenerated(line));
1299
+ const join = (path) => path.join("\x00");
1300
+ const wantedPaths = wanted.map(keyOf2);
1301
+ const wantedKeys = new Set(wantedPaths.map(join));
1302
+ const existingKeys = new Set(existing.map((line) => join(keyOf2(line))));
1303
+ const wantedUnder = (path) => wantedPaths.filter((other) => startsWith(other, path));
1304
+ const sections = new Map;
1305
+ const sectionFor = (path) => {
1306
+ const name = sectionOf(path);
1307
+ if (!sections.has(name))
1308
+ sections.set(name, { show: [], activate: [], remove: [], add: [] });
1309
+ return sections.get(name);
1310
+ };
1311
+ for (const path of wantedPaths)
1312
+ sectionFor(path);
1313
+ const rewritten = [];
1314
+ const blocks = new Map;
1315
+ for (const path of wantedPaths) {
1316
+ const ordered = orderedBlockOf(path);
1317
+ if (ordered)
1318
+ blocks.set(join(ordered.block), ordered.block);
1319
+ }
1320
+ for (const block of blocks.values()) {
1321
+ const inBlock = (line) => startsWith(keyOf2(line), block);
1322
+ const order = (all) => [...new Set(all.filter(inBlock).map((line) => orderedBlockOf(keyOf2(line)).item))];
1323
+ const keys = (all) => all.filter(inBlock).map((line) => join(keyOf2(line))).sort().join(`
1324
+ `);
1325
+ const wantedHere = wanted.filter(inBlock);
1326
+ const existingHere = existing.filter(inBlock);
1327
+ if (join(order(wanted)) === join(order(existing)) && keys(wanted) === keys(existing))
1328
+ continue;
1329
+ rewritten.push(block);
1330
+ const section = sectionFor(block);
1331
+ if (existingHere.length)
1332
+ section.remove.push(`delete ${wordsOf(existingHere[0]).slice(1, 1 + block.length).join(" ")}`);
1333
+ section.show.push(...existingHere.map((line) => `- ${line}`), ...wantedHere.map((line) => `+ ${line}`));
1334
+ section.add.push(...wantedHere);
1335
+ }
1336
+ const isRewritten = (line) => rewritten.some((block) => startsWith(keyOf2(line), block));
1337
+ const removed = [];
1338
+ for (const line of existing) {
1339
+ if (isRewritten(line))
1340
+ continue;
1341
+ const path = keyOf2(line);
1342
+ if (wantedKeys.has(join(path)))
1343
+ continue;
1344
+ if (wantedUnder(path).length)
1345
+ continue;
1346
+ let length = 1;
1347
+ while (wantedUnder(path.slice(0, length)).length)
1348
+ length++;
1349
+ while (length < path.length && !canStopAt(path, length - 1))
1350
+ length++;
1351
+ const hidden = path.indexOf("<secret>");
1352
+ if (hidden >= 0 && hidden < length && !NAMED_LISTS.has(path[hidden - 1]))
1353
+ length = hidden;
1354
+ removed.push(wordsOf(line).slice(1, 1 + length));
1355
+ }
1356
+ const isInside = (path, other) => other.length < path.length && other.every((word, index) => bare(path[index]) === bare(word));
1357
+ const removals = removed.filter((path, index) => !removed.some((other) => isInside(path, other)) && removed.findIndex((other) => join(other) === join(path)) === index);
1358
+ const isRemoved = (line) => removals.some((path) => startsWith(keyOf2(line), path.map(bare)));
1359
+ for (const path of removals) {
1360
+ const section = sectionFor(path.map(bare));
1361
+ section.remove.push(`delete ${path.join(" ")}`);
1362
+ const under = existing.filter((line) => startsWith(keyOf2(line), path.map(bare)));
1363
+ section.show.push(...under.map((line) => `- ${line}`));
1364
+ }
1365
+ for (const line of deactivated) {
1366
+ const path = wordsOf(line).slice(1);
1367
+ if (!wantedUnder(path.map(bare)).length)
1368
+ continue;
1369
+ const section = sectionFor(path.map(bare));
1370
+ section.activate.push(`activate ${path.join(" ")}`);
1371
+ section.show.push(`+ activate ${path.join(" ")}`);
1372
+ }
1373
+ const extended = (path) => printed.some((line) => !isRemoved(line) && startsWith(keyOf2(line), path));
1374
+ for (const line of wanted) {
1375
+ if (isRewritten(line))
1376
+ continue;
1377
+ const path = keyOf2(line);
1378
+ const isNew = !existingKeys.has(join(path)) && !extended(path);
1379
+ const rotated = options.secrets && line.includes("<secret:");
1380
+ if (!isNew && !rotated)
1381
+ continue;
1382
+ const section = sectionFor(path);
1383
+ section.add.push(line);
1384
+ section.show.push(`+ ${line}`);
1385
+ const clear = "delete snmp community <redacted>";
1386
+ if (rotated && line.startsWith("set snmp community ") && !section.remove.includes(clear))
1387
+ section.remove.push(clear);
1388
+ }
1389
+ const steps = [...sections].filter(([, section]) => section.show.length).map(([title, section]) => ({ title, show: section.show, send: [...section.activate, ...section.remove, ...section.add] }));
1390
+ return { steps, problems: [] };
1391
+ }
1392
+ async function login(device) {
1393
+ const user = device.users?.[device.connection.user];
1394
+ if (!user)
1395
+ throw new Error(`${device.name}: connection user ${device.connection.user} is not one of its users`);
1396
+ return connect({
1397
+ host: device.connection.host,
1398
+ port: device.connection.port,
1399
+ user: device.connection.user,
1400
+ password: resolve(user.password)
1401
+ });
1402
+ }
1403
+ async function checkVersion(client, device) {
1404
+ const version = await exec(client, "show version | match Junos:");
1405
+ const release = /Junos: (\d+)\.(\S+)/.exec(version);
1406
+ if (!release)
1407
+ throw new Error(`${device.name} did not say which Junos it runs: ${version.slice(0, 200)}`);
1408
+ if (Number(release[1]) !== SUPPORTED_MAJOR) {
1409
+ throw new Error(`${device.name} runs Junos ${release[1]}.${release[2]}; this adapter renders for Junos ${SUPPORTED_MAJOR}`);
1410
+ }
1411
+ }
1412
+ async function read(device) {
1413
+ const client = await login(device);
1414
+ try {
1415
+ await checkVersion(client, device);
1416
+ const output = await exec(client, "show configuration | display set | no-more", 180000);
1417
+ if (!/^set /m.test(output))
1418
+ throw new Error(`${device.name} returned no configuration`);
1419
+ return clean(output);
1420
+ } finally {
1421
+ client.end();
1422
+ }
1423
+ }
1424
+ var PROMPT = /[>#%] $/;
1425
+ async function run(channel, line, timeout = 60000) {
1426
+ channel.write(`${line}
1427
+ `);
1428
+ const output = await readUntil(channel, (text) => PROMPT.test(text.replace(/\r/g, "")), timeout, 300);
1429
+ return output.replace(/\r/g, "");
1430
+ }
1431
+ var EDIT_PROMPT = /\[edit[^\]\n]*\]\n[^\n]*# $/;
1432
+ async function edit(channel, line, timeout = 60000) {
1433
+ channel.write(`${line}
1434
+ `);
1435
+ const output = await readUntil(channel, (text) => EDIT_PROMPT.test(text.replace(/\r/g, "")), timeout, 300);
1436
+ return output.replace(/\r/g, "");
1437
+ }
1438
+ async function openCli(channel) {
1439
+ const banner = await readUntil(channel, (text) => PROMPT.test(text.replace(/\r/g, "")), 20000, 500);
1440
+ if (/% $/.test(banner.replace(/\r/g, "")))
1441
+ await run(channel, "cli");
1442
+ await run(channel, "set cli screen-length 0");
1443
+ await run(channel, "set cli screen-width 0");
1444
+ await run(channel, "set cli complete-on-space off");
1445
+ }
1446
+ async function named(channel, line) {
1447
+ const words = wordsOf(line).slice(1);
1448
+ const at = words.indexOf("<redacted>");
1449
+ if (at < 0)
1450
+ return [line];
1451
+ const shown = await edit(channel, `show ${words.slice(0, at - 1).join(" ")} | display set`);
1452
+ const inList = (statement) => statement[0] === "set" && words.slice(0, at).every((word, index) => statement[index + 1] === word);
1453
+ const names = new Set(shown.split(`
1454
+ `).map(wordsOf).filter(inList).map((statement) => statement[at + 1]));
1455
+ return [...names].map((name) => `delete ${[...words.slice(0, at), name, ...words.slice(at + 1)].join(" ")}`);
1456
+ }
1457
+ var REFUSED = /^terminal:\d+:\(\d+\) (?!warning)|^error: /m;
1458
+ async function configure(channel, plan, guard) {
1459
+ const sent = plan.steps.flatMap((step) => step.send);
1460
+ const entered = await run(channel, "configure exclusive");
1461
+ if (!/Entering configuration mode/.test(entered) || /^error: /m.test(entered)) {
1462
+ throw new Error(`the candidate could not be locked: ${entered.trim().slice(0, 300)}`);
1463
+ }
1464
+ const deletions = [];
1465
+ for (const line of sent.filter((line) => line.startsWith("delete ")))
1466
+ deletions.push(...await named(channel, line));
1467
+ const ordered = [
1468
+ ...sent.filter((line) => line.startsWith("activate ")),
1469
+ ...deletions,
1470
+ ...sent.filter((line) => line.startsWith("set "))
1471
+ ];
1472
+ const discard = async (reason) => {
1473
+ await edit(channel, "rollback 0").catch(() => {
1474
+ return;
1475
+ });
1476
+ await run(channel, "exit configuration-mode").catch(() => {
1477
+ return;
1478
+ });
1479
+ return new Error(reason);
1480
+ };
1481
+ channel.write(`load set terminal
1482
+ `);
1483
+ await readUntil(channel, (text) => text.includes("[Type ^D at a new line to end input]"), 20000, 300);
1484
+ for (const line of ordered)
1485
+ channel.write(`${substitute(line)}
1486
+ `);
1487
+ const loaded = await edit(channel, "\x04", 300000);
1488
+ const refused = loaded.split(`
1489
+ `).filter((line) => REFUSED.test(line));
1490
+ if (refused.length)
1491
+ throw await discard(`the device refused the plan, nothing was applied:
1492
+ ${refused.join(`
1493
+ `)}`);
1494
+ const checked = await edit(channel, "commit check", 600000);
1495
+ if (!/configuration check succeeds/.test(checked)) {
1496
+ throw await discard(`commit check failed, nothing was applied:
1497
+ ${checked.trim().split(`
1498
+ `).slice(1, 20).join(`
1499
+ `)}`);
1500
+ }
1501
+ if (!guard) {
1502
+ const committed = await edit(channel, "commit comment circuit", 600000);
1503
+ if (!/commit complete/.test(committed))
1504
+ throw await discard(`commit failed, nothing was applied:
1505
+ ${committed.trim().slice(0, 300)}`);
1506
+ await run(channel, "exit configuration-mode");
1507
+ return;
1508
+ }
1509
+ const { minutes, prove } = guard;
1510
+ const committed = await edit(channel, `commit confirmed ${minutes} comment circuit`, 600000);
1511
+ if (!/commit complete/.test(committed)) {
1512
+ throw await discard(`commit failed, nothing was applied:
1513
+ ${committed.trim().split(`
1514
+ `).slice(1, 20).join(`
1515
+ `)}`);
1516
+ }
1517
+ console.log(` committed; it rolls back in ${minutes} minutes unless access is proved`);
1518
+ try {
1519
+ await prove();
1520
+ } catch (error) {
1521
+ await edit(channel, "rollback 1", 60000).catch(() => {
1522
+ return;
1523
+ });
1524
+ await edit(channel, "commit comment circuit-restore", 600000).catch(() => {
1525
+ return;
1526
+ });
1527
+ await run(channel, "exit configuration-mode").catch(() => {
1528
+ return;
1529
+ });
1530
+ throw new Error(`a fresh login failed (${error.message}); the previous configuration is restored, at the latest in ${minutes} minutes`);
1531
+ }
1532
+ const confirmed = await edit(channel, "commit comment circuit-confirmed", 600000);
1533
+ if (!/commit complete/.test(confirmed)) {
1534
+ throw new Error(`access was proved but the confirmation failed; the device rolls back in ${minutes} minutes:
1535
+ ${confirmed.trim().slice(0, 300)}`);
1536
+ }
1537
+ await run(channel, "exit configuration-mode");
1538
+ console.log(" logged in again, commit confirmed");
1539
+ }
1540
+ async function apply(device, _network, plan, options) {
1541
+ const client = await login(device);
1542
+ try {
1543
+ await checkVersion(client, device);
1544
+ const channel = await shell(client);
1545
+ await openCli(channel);
1546
+ await configure(channel, plan, {
1547
+ minutes: options.rollback,
1548
+ prove: async () => {
1549
+ const proof = await login(device);
1550
+ proof.end();
1551
+ }
1552
+ });
1553
+ channel.end();
1554
+ } finally {
1555
+ client.end();
1556
+ }
1557
+ }
1558
+ function unsupported(device) {
1559
+ const found = [];
1560
+ const names = new Names(device);
1561
+ const users = device.users ?? {};
1562
+ const root = users.root;
1563
+ if (!root)
1564
+ found.push("users without root, the platform needs a root password");
1565
+ if (root && root.role !== "admin")
1566
+ found.push("users.root.role, root is always admin");
1567
+ if (root?.description)
1568
+ found.push("users.root.description");
1569
+ if (device.connection.user === "root")
1570
+ found.push("connection.user root, which logs in to a shell rather than the CLI");
1571
+ for (const [name, user] of Object.entries(users)) {
1572
+ if (SYSTEM_ACCOUNTS.has(name))
1573
+ found.push(`users.${name}, a name the platform keeps for a system account`);
1574
+ for (const key of user.ssh_keys ?? []) {
1575
+ const kind = key.split(" ")[0];
1576
+ if (!KEY_STATEMENT[kind])
1577
+ found.push(`users.${name}.ssh_keys, a key of type ${kind}`);
1578
+ }
1579
+ }
1580
+ const management = device.management ?? {};
1581
+ for (const field of ["https", "api_tls", "privilege_password"]) {
1582
+ if (management[field])
1583
+ found.push(`management.${field}`);
1584
+ }
1585
+ if (management.console)
1586
+ found.push("management.console, the console uses the device's users");
1587
+ if (management.native?.interfaces?.length)
1588
+ found.push("management.native.interfaces, NETCONF runs over IP only");
1589
+ if (management.ssh?.auth_timeout || management.ssh?.auth_retries)
1590
+ found.push("management.ssh.auth_timeout and auth_retries");
1591
+ for (const service of ["telnet", "ftp", "snmp"]) {
1592
+ if (management[service]?.port)
1593
+ found.push(`management.${service}.port`);
1594
+ }
1595
+ const system = device.system ?? {};
1596
+ if (system.release_channel)
1597
+ found.push("system.release_channel");
1598
+ if (system.transceiver_monitoring !== undefined)
1599
+ found.push("system.transceiver_monitoring, it is always on");
1600
+ if (system.dns?.serve)
1601
+ found.push("system.dns.serve");
1602
+ if (system.ip?.syn_cookies)
1603
+ found.push("system.ip.syn_cookies");
1604
+ if (system.logging?.local?.lines_per_file !== undefined)
1605
+ found.push("system.logging.local.lines_per_file, files rotate by size");
1606
+ if (system.ntp && !system.ntp.serve && system.ntp.servers.some((server) => !isAddress2(server))) {
1607
+ found.push("system.ntp.servers by name while not serving, the filter keeping others out needs addresses");
1608
+ }
1609
+ for (const [name, port] of Object.entries(device.ports ?? {})) {
1610
+ if (!port)
1611
+ continue;
1612
+ const physical = names.port(name);
1613
+ if (physical.startsWith("em")) {
1614
+ for (const field of [
1615
+ "access_vlan",
1616
+ "trunk_vlans",
1617
+ "native_vlan",
1618
+ "lag",
1619
+ "stp",
1620
+ "storm_control",
1621
+ "speed",
1622
+ "vrrp",
1623
+ "vrf",
1624
+ "mtu",
1625
+ "acl",
1626
+ "ipv6_ra"
1627
+ ]) {
1628
+ if (port[field] !== undefined)
1629
+ found.push(`ports.${name}.${field}, a management port only routes`);
1630
+ }
1631
+ }
1632
+ if (port.speed && !(port.speed === "1g" && physical.startsWith("ge-")))
1633
+ found.push(`ports.${name}.speed ${port.speed}, only a 1G optic in a 10G cage`);
1634
+ if (port.hardware_offload !== undefined)
1635
+ found.push(`ports.${name}.hardware_offload`);
1636
+ const acl = port.acl ? device.acls?.[port.acl] : undefined;
1637
+ if (acl?.some((rule) => rule.match?.vlan))
1638
+ found.push(`acls.${port.acl} matching a VLAN, on port ${name}`);
1639
+ found.push(...routedGaps(`ports.${name}`, port));
1640
+ found.push(...switchedGaps(`ports.${name}`, port, device));
1641
+ }
1642
+ const loopbackTables = new Set;
1643
+ for (const [name, iface] of Object.entries(device.interfaces ?? {})) {
1644
+ if (iface.type === "vxlan" || iface.type === "wireguard") {
1645
+ found.push(`interfaces.${name}, a ${iface.type}`);
1646
+ continue;
1647
+ }
1648
+ if (iface.hardware_offload !== undefined)
1649
+ found.push(`interfaces.${name}.hardware_offload`);
1650
+ if (iface.type === "loopback" && iface.ipv6_ra)
1651
+ found.push(`interfaces.${name}.ipv6_ra, on a loopback`);
1652
+ if (iface.type === "loopback" && iface.mtu !== undefined)
1653
+ found.push(`interfaces.${name}.mtu, on a loopback`);
1654
+ if (iface.type === "gre" && iface.vrrp?.length)
1655
+ found.push(`interfaces.${name}.vrrp, on a tunnel`);
1656
+ if (iface.type === "vlan" && iface.mtu !== undefined) {
1657
+ const smallest = Math.min(...carriers(device, iface.vlan));
1658
+ if (iface.mtu > smallest)
1659
+ found.push(`interfaces.${name}.mtu ${iface.mtu}, above the ${smallest} its VLAN's ports carry`);
1660
+ }
1661
+ found.push(...routedGaps(`interfaces.${name}`, iface));
1662
+ if (iface.type === "lag")
1663
+ found.push(...switchedGaps(`interfaces.${name}`, iface, device));
1664
+ if (iface.type === "loopback") {
1665
+ const table = iface.vrf ?? "";
1666
+ if (loopbackTables.has(table))
1667
+ found.push(`interfaces.${name}, a second loopback in one routing table`);
1668
+ loopbackTables.add(table);
1669
+ if (iface.vrrp?.length)
1670
+ found.push(`interfaces.${name}.vrrp, on a loopback`);
1671
+ }
1672
+ }
1673
+ const ids = [...lagIds(device).values()];
1674
+ if (ids.length !== new Set(ids).size)
1675
+ found.push("interfaces, two LAGs with one number");
1676
+ if (device.stp?.priority !== undefined && device.stp.priority % 4096 !== 0)
1677
+ found.push("stp.priority, in steps of 4096");
1678
+ const routing = device.routing;
1679
+ found.push(...bgpGaps(device));
1680
+ const tables = new Map;
1681
+ for (const route of routing?.static ?? []) {
1682
+ if (route.description)
1683
+ found.push(`routing.static ${route.prefix} description`);
1684
+ const table = `${route.vrf ?? ""} ${split(route.prefix).addr}/${split(route.prefix).len}`;
1685
+ tables.set(table, (tables.get(table) ?? 0) + 1);
1686
+ }
1687
+ for (const route of routing?.static ?? []) {
1688
+ const table = `${route.vrf ?? ""} ${split(route.prefix).addr}/${split(route.prefix).len}`;
1689
+ if (route.blackhole && tables.get(table) > 1)
1690
+ found.push(`routing.static ${route.prefix}, a blackhole beside another route to the same prefix`);
1691
+ }
1692
+ for (const area of Object.values(routing?.ospf?.areas ?? {})) {
1693
+ for (const iface of Object.keys(area.interfaces)) {
1694
+ const settings = device.interfaces?.[iface] ?? device.ports?.[iface];
1695
+ if (settings?.vrf)
1696
+ found.push(`routing.ospf on ${iface}, which routes in a VRF`);
1697
+ }
1698
+ }
1699
+ for (const field of ["firewall", "hardware_offload", "certificates"]) {
1700
+ if (device[field] !== undefined)
1701
+ found.push(field);
1702
+ }
1703
+ const relayed = new Set(Object.values(device.dhcp_relay ?? {}).map((relay) => relay.interface));
1704
+ for (const [name, server] of Object.entries(device.dhcp ?? {})) {
1705
+ if (relayed.has(server.interface))
1706
+ found.push(`dhcp.${name}.interface, which a DHCP relay also uses`);
1707
+ const settings = device.interfaces?.[server.interface] ?? device.ports?.[server.interface];
1708
+ if (settings?.vrf)
1709
+ found.push(`dhcp.${name}.interface, which routes in a VRF`);
1710
+ if (familyOf2(host2(server.network)) === "ipv6")
1711
+ found.push(`dhcp.${name}.network, IPv6`);
1712
+ }
1713
+ for (const [name, relay] of Object.entries(device.dhcp_relay ?? {})) {
1714
+ if (relay.servers.some((server) => familyOf2(server) === "ipv6"))
1715
+ found.push(`dhcp_relay.${name}.servers, IPv6`);
1716
+ }
1717
+ const flow = device.flow_export;
1718
+ if (flow && flow.protocol !== "sflow")
1719
+ found.push(`flow_export.protocol ${flow.protocol}, the platform exports sFlow`);
1720
+ if (flow?.protocol === "sflow" && flow.sampling === undefined)
1721
+ found.push("flow_export.sampling, the platform samples");
1722
+ for (const name of flow?.interfaces ?? []) {
1723
+ if (names.members(name).some((port) => port.startsWith("em")))
1724
+ found.push(`flow_export.interfaces ${name}, a management port`);
1725
+ }
1726
+ return found;
1727
+ }
1728
+ function carriers(device, vlan) {
1729
+ const sizes = [1500];
1730
+ const carrying = [...Object.values(device.ports ?? {}), ...Object.values(device.interfaces ?? {})];
1731
+ for (const settings of carrying) {
1732
+ if (!settings || !(("access_vlan" in settings) || ("trunk_vlans" in settings)))
1733
+ continue;
1734
+ const members = [settings.access_vlan, settings.native_vlan, ...settings.trunk_vlans ?? []];
1735
+ if (members.includes(vlan))
1736
+ sizes.push(settings.mtu ?? 1500);
1737
+ }
1738
+ return sizes.length > 1 ? sizes.slice(1) : sizes;
1739
+ }
1740
+ function bgpGaps(device) {
1741
+ const found = [];
1742
+ const bgp = device.routing?.bgp;
1743
+ for (const [name, neighbor] of Object.entries(bgp?.neighbors ?? {})) {
1744
+ const settings = settingsOf(device, neighbor);
1745
+ if (settings.role)
1746
+ found.push(`routing.bgp.neighbors.${name}.role, Junos 21 has no BGP roles`);
1747
+ if (familyOf2(neighbor.address) === "ipv6" && settings.families?.includes("ipv4")) {
1748
+ found.push(`routing.bgp.neighbors.${name}.families ipv4 over an IPv6 session`);
1749
+ }
1750
+ if (settings.keepalive !== undefined)
1751
+ found.push(`routing.bgp.neighbors.${name}.keepalive, the platform keeps it at a third of hold_time`);
1752
+ }
1753
+ for (const server of device.routing?.rpki ?? []) {
1754
+ if (server.description)
1755
+ found.push(`routing.rpki ${server.address} description`);
1756
+ }
1757
+ for (const [name, rules] of Object.entries(device.policies ?? {})) {
1758
+ rules.forEach((rule, index) => {
1759
+ const where = `policies.${name}[${index}]`;
1760
+ if (rule.set?.preferred_source)
1761
+ found.push(`${where}.set.preferred_source`);
1762
+ if (rule.match?.prefix && rule.match.prefix_length)
1763
+ found.push(`${where}.match.prefix with prefix_length`);
1764
+ });
1765
+ }
1766
+ return found;
1767
+ }
1768
+ function routedGaps(where, settings) {
1769
+ const found = [];
1770
+ const addresses = (settings.addresses ?? []).map((entry) => typeof entry === "string" ? entry : entry.address);
1771
+ if (settings.addresses?.some((entry) => typeof entry !== "string"))
1772
+ found.push(`${where}.addresses with a peer`);
1773
+ for (const group of settings.vrrp ?? []) {
1774
+ if (familyOf2(group.address) === "ipv6")
1775
+ found.push(`${where}.vrrp ${group.id}, IPv6`);
1776
+ else if (!vrrpAddress(addresses, group))
1777
+ found.push(`${where}.vrrp ${group.id}, no address of the interface holds ${group.address}`);
1778
+ }
1779
+ return found;
1780
+ }
1781
+ function switchedGaps(where, settings, device) {
1782
+ const found = [];
1783
+ const storm = Object.values(settings.storm_control ?? {});
1784
+ if (storm.some((threshold) => ("pps" in threshold)))
1785
+ found.push(`${where}.storm_control in pps, the platform takes percent`);
1786
+ else if (new Set(storm.map((threshold) => ("percent" in threshold) ? threshold.percent : 0)).size > 1) {
1787
+ found.push(`${where}.storm_control, one threshold for every kind`);
1788
+ }
1789
+ if (typeof settings.stp === "object" && !device.stp)
1790
+ found.push(`${where}.stp while spanning tree is off on the device`);
1791
+ return found;
1792
+ }
1793
+ function fillPlan(set, prefixes) {
1794
+ const list = `policy-options prefix-list ${set}`;
1795
+ const send = [`delete ${list}`, `set ${list}`, ...prefixes.map((prefix) => `set ${list} ${prefix}`)];
1796
+ return { steps: [{ title: list, show: [], send }], problems: [] };
1797
+ }
1798
+ async function fill(device, _network, set, _family, prefixes) {
1799
+ const client = await login(device);
1800
+ try {
1801
+ await checkVersion(client, device);
1802
+ const channel = await shell(client);
1803
+ await openCli(channel);
1804
+ await configure(channel, fillPlan(set, prefixes));
1805
+ channel.end();
1806
+ console.log(` ${set}: ${prefixes.length} prefixes on the device`);
1807
+ } finally {
1808
+ client.end();
1809
+ }
1810
+ }
1811
+ var junos = {
1812
+ extension: ".conf",
1813
+ unsupported,
1814
+ render,
1815
+ read,
1816
+ plan,
1817
+ apply,
1818
+ fill
1819
+ };
1820
+
1821
+ // src/core/time.ts
1822
+ function offset(zone) {
1823
+ const name = new Intl.DateTimeFormat("en-US", {
1824
+ timeZone: zone,
1825
+ timeZoneName: "longOffset"
1826
+ }).formatToParts(new Date).find((entry) => entry.type === "timeZoneName").value;
1827
+ const found = /GMT([+-])(\d{2}):(\d{2})/.exec(name);
1828
+ if (!found)
1829
+ return 0;
1830
+ const [, sign, hours, minutes] = found;
1831
+ const sizeInMinutes = Number(hours) * 60 + Number(minutes);
1832
+ return sign === "-" ? -sizeInMinutes : sizeInMinutes;
1833
+ }
1834
+
267
1835
  // src/adapters/devices/lines.ts
268
1836
  var indentOf = (raw) => raw.length - raw.trimStart().length;
269
1837
  function entries(text, dialect) {
@@ -391,10 +1959,10 @@ function steps(groups, dialect, around = nothingAround) {
391
1959
  });
392
1960
  }
393
1961
  var STORED_CREDENTIAL = /(password (?:irreversible-)?cipher|community read cipher|^snmp-server community)( +)\S+/gm;
394
- var scrub = (text) => text.replace(STORED_CREDENTIAL, "$1$2<redacted>");
1962
+ var scrub2 = (text) => text.replace(STORED_CREDENTIAL, "$1$2<redacted>");
395
1963
 
396
1964
  // src/adapters/devices/raisecom-ros/index.ts
397
- var DEVICE_FACTS = [
1965
+ var DEVICE_FACTS2 = [
398
1966
  "Being processed",
399
1967
  "System current configuration",
400
1968
  "recv-mode",
@@ -447,7 +2015,7 @@ var dialect = {
447
2015
  block: /^(interface |access-list |router ospf )/,
448
2016
  indented: false,
449
2017
  terminator: "!",
450
- ignore: (line) => line.startsWith("!") || DEVICE_FACTS.some((fact) => line.startsWith(fact)),
2018
+ ignore: (line) => line.startsWith("!") || DEVICE_FACTS2.some((fact) => line.startsWith(fact)),
451
2019
  undo,
452
2020
  leave: "exit",
453
2021
  canon: (line) => line.replace(/password (cipher )?(\S+)/, "password <secret>").replace(/community <secret:\w+>/, "community <redacted>").replace(/^snmp-server user (\S+) (?:authentication|authkey) (\S+) \S+ (?:privacy|privkey)\s+(\S+) \S+$/, "snmp-server user $1 $2 $3 <secret>").replace(/^(snmp-server access \S+ read \S+) notify internet (usm \S+)$/, "$1 $2"),
@@ -463,8 +2031,8 @@ var dialect = {
463
2031
  }
464
2032
  };
465
2033
  var OSPF_PROCESS = 1;
466
- var SNMP_GROUP = "circuit";
467
- var SNMP_AUTH = { sha1: "sha" };
2034
+ var SNMP_GROUP2 = "circuit";
2035
+ var SNMP_AUTH2 = { sha1: "sha" };
468
2036
  var MANAGEMENT_ACL = 1000;
469
2037
  function vlanList(ids) {
470
2038
  const sorted = [...new Set(ids)].sort((a, b) => a - b);
@@ -478,7 +2046,7 @@ function vlanList(ids) {
478
2046
  }
479
2047
  return ranges.join(",");
480
2048
  }
481
- function render(device) {
2049
+ function render2(device) {
482
2050
  const lines = [];
483
2051
  const vlans = device.vlans ?? {};
484
2052
  const vlanId = (name) => vlans[name].id;
@@ -496,7 +2064,7 @@ function render(device) {
496
2064
  lines.push(`description ${lag.description}`);
497
2065
  if (lag.mode === "static")
498
2066
  lines.push("mode manual");
499
- lines.push(...switching(lag, vlanId), "!");
2067
+ lines.push(...switching2(lag, vlanId), "!");
500
2068
  }
501
2069
  if (allow.length) {
502
2070
  lines.push(`access-list ${MANAGEMENT_ACL}`, " description management");
@@ -570,11 +2138,11 @@ function render(device) {
570
2138
  lines.push(`snmp-server community ${marker(snmp.community)} ro`);
571
2139
  const snmpUsers = Object.entries(snmp?.users ?? {});
572
2140
  if (snmpUsers.length)
573
- lines.push(`snmp-server access ${SNMP_GROUP} read internet usm authpriv`);
2141
+ lines.push(`snmp-server access ${SNMP_GROUP2} read internet usm authpriv`);
574
2142
  for (const [name, user] of snmpUsers) {
575
- const authentication = `authentication ${SNMP_AUTH[user.auth]} ${marker(user.auth_password)}`;
2143
+ const authentication = `authentication ${SNMP_AUTH2[user.auth]} ${marker(user.auth_password)}`;
576
2144
  const privacy = `privacy ${user.privacy} ${marker(user.privacy_password)}`;
577
- lines.push(`snmp-server user ${name} ${authentication} ${privacy}`, `snmp-server group ${SNMP_GROUP} user ${name} usm`);
2145
+ lines.push(`snmp-server user ${name} ${authentication} ${privacy}`, `snmp-server group ${SNMP_GROUP2} user ${name} usm`);
578
2146
  }
579
2147
  if ((snmp?.community || snmpUsers.length) && allow.length)
580
2148
  lines.push(`snmp-server access-list ${MANAGEMENT_ACL}`);
@@ -603,7 +2171,7 @@ function render(device) {
603
2171
  `) + `
604
2172
  `;
605
2173
  }
606
- function switching(settings, vlanId) {
2174
+ function switching2(settings, vlanId) {
607
2175
  const lines = [];
608
2176
  if (settings.access_vlan)
609
2177
  lines.push(`switchport access vlan ${vlanId(settings.access_vlan)}`);
@@ -632,7 +2200,7 @@ function physicalPort(settings, vlanId, lagNumbers) {
632
2200
  lines.push(`description ${settings.description}`);
633
2201
  if (settings.lag)
634
2202
  return [...lines, `port-channel ${lagNumbers.get(settings.lag)}`];
635
- lines.push(...switching(settings, vlanId));
2203
+ lines.push(...switching2(settings, vlanId));
636
2204
  if (settings.lldp === false)
637
2205
  lines.push("lldp disable");
638
2206
  if (settings.stp === false)
@@ -668,7 +2236,7 @@ async function open(device) {
668
2236
  await send(channel, "terminal page-break disable", 15000, 1000);
669
2237
  return { channel, close };
670
2238
  }
671
- async function read(device) {
2239
+ async function read2(device) {
672
2240
  const { channel, close } = await open(device);
673
2241
  try {
674
2242
  const dump = await send(channel, "show running-config", 150000, 6000);
@@ -680,13 +2248,13 @@ async function read(device) {
680
2248
  const body = lines.slice(start).filter((line) => line.startsWith("!") || !/[#>]\s*$/.test(line.trim()));
681
2249
  const text = body.join(`
682
2250
  `).replace(/^!(System (up )?time|Boot times):.*\n/gm, "").trim();
683
- return scrub(text + `
2251
+ return scrub2(text + `
684
2252
  `).replace(/(authkey \S+|privkey\s+\S+) \S+/g, "$1 <redacted>");
685
2253
  } finally {
686
2254
  close();
687
2255
  }
688
2256
  }
689
- function unsupported(device) {
2257
+ function unsupported2(device) {
690
2258
  const found = [];
691
2259
  for (const [name, port] of Object.entries(device.ports ?? {})) {
692
2260
  if (!port)
@@ -779,7 +2347,7 @@ function unsupported(device) {
779
2347
  found.push(`management.${field}`);
780
2348
  }
781
2349
  for (const [name, user] of Object.entries(management.snmp?.users ?? {})) {
782
- if (!SNMP_AUTH[user.auth])
2350
+ if (!SNMP_AUTH2[user.auth])
783
2351
  found.push(`management.snmp.users.${name}.auth ${user.auth}, the platform authenticates with SHA1 or MD5`);
784
2352
  }
785
2353
  if (management.snmp?.contact || management.snmp?.location)
@@ -801,10 +2369,10 @@ function unsupported(device) {
801
2369
  found.push(...switchUnsupported(device));
802
2370
  return found;
803
2371
  }
804
- var REFUSED = /^(%|Error|Incomplete|Unrecognized)|can not|cannot |is in use|Wrong parameter|invalid|unsuccessful/im;
2372
+ var REFUSED2 = /^(%|Error|Incomplete|Unrecognized)|can not|cannot |is in use|Wrong parameter|invalid|unsuccessful/im;
805
2373
  var CONFIRM = /^(portswitch|switchport trunk allowed vlan|switchport mode)/;
806
2374
  var RELEASE = /^no (\S+ access-list \d+)$/;
807
- async function apply(device, _network, plan) {
2375
+ async function apply2(device, _network, plan) {
808
2376
  const { channel, close } = await open(device);
809
2377
  const released = [];
810
2378
  try {
@@ -815,7 +2383,7 @@ async function apply(device, _network, plan) {
815
2383
  const asks = /[[(]y\/n[\])]|input 'y'/i.test(answer) || CONFIRM.test(line) && !/set successfully/i.test(answer);
816
2384
  if (asks)
817
2385
  answer += await send(channel, "y", 1e4, 1000);
818
- if (REFUSED.test(answer))
2386
+ if (REFUSED2.test(answer))
819
2387
  throw new Error(`${step.title}: ${line}
820
2388
  device said: ${answer.trim().slice(0, 300)}`);
821
2389
  const release = RELEASE.exec(line);
@@ -863,7 +2431,7 @@ function createdIds(line) {
863
2431
  }
864
2432
  return ids;
865
2433
  }
866
- function plan(desired, current) {
2434
+ function plan2(desired, current) {
867
2435
  const groups = compare(desired, current, dialect);
868
2436
  const existing = entries(current, dialect);
869
2437
  for (const group of groups) {
@@ -913,7 +2481,7 @@ function plan(desired, current) {
913
2481
  }
914
2482
  return { steps: planned, problems: [] };
915
2483
  }
916
- var raisecom = { extension: ".txt", unsupported, render, read, plan, apply };
2484
+ var raisecom = { extension: ".txt", unsupported: unsupported2, render: render2, read: read2, plan: plan2, apply: apply2 };
917
2485
 
918
2486
  // src/adapters/devices/routeros/export.ts
919
2487
  var unwrap = (text) => text.replace(/\r/g, "").replace(/\\\n\s*/g, "");
@@ -1230,9 +2798,9 @@ var DYNAMIC = new Set([
1230
2798
  "/ip dhcp-server lease",
1231
2799
  "/interface bridge vlan"
1232
2800
  ]);
1233
- var quote = (value) => `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$")}"`;
1234
- var value = (raw) => /^[\w.:/@,!-]+$/.test(raw) ? raw : quote(raw);
1235
- var findValue = (raw) => /^[\w.:-]+$/.test(raw) ? raw : quote(raw);
2801
+ var quote2 = (value) => `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\$/g, "\\$")}"`;
2802
+ var value = (raw) => /^[\w.:/@,!-]+$/.test(raw) ? raw : quote2(raw);
2803
+ var findValue = (raw) => /^[\w.:-]+$/.test(raw) ? raw : quote2(raw);
1236
2804
  function find(menu, id) {
1237
2805
  const conditions = Object.entries(id).map(([property, raw]) => `${property}=${findValue(raw)}`);
1238
2806
  if (DYNAMIC.has(menu))
@@ -1347,7 +2915,7 @@ function planEntries(menu, wanted, existing, sendSecrets, problems) {
1347
2915
  for (const list of wholeLists) {
1348
2916
  const count = removed.filter((entry) => entry.attrs.list === list).length;
1349
2917
  removeStep.show.push(`- list ${list}, ${count} entr${count === 1 ? "y" : "ies"}`);
1350
- removeStep.send.push(`${menu} remove [ find list=${quote(list)} and !dynamic ]`);
2918
+ removeStep.send.push(`${menu} remove [ find list=${quote2(list)} and !dynamic ]`);
1351
2919
  }
1352
2920
  for (const entry of removed) {
1353
2921
  if (wholeLists.has(entry.attrs.list ?? ""))
@@ -1379,19 +2947,19 @@ function planCertificates(wanted, existing) {
1379
2947
  const oldName = sameCertificate.attrs.name;
1380
2948
  renamed.add(oldName);
1381
2949
  install.show.push(`~ rename ${oldName} to ${name}`);
1382
- install.send.push(`${menu} set [ find name=${quote(oldName)} ] name=${quote(name)}`);
2950
+ install.send.push(`${menu} set [ find name=${quote2(oldName)} ] name=${quote2(name)}`);
1383
2951
  continue;
1384
2952
  }
1385
2953
  install.show.push(`${current ? "~" : "+"} install ${name}`);
1386
2954
  if (current)
1387
- install.send.push(`${menu} remove [ find name=${quote(name)} ]`);
2955
+ install.send.push(`${menu} remove [ find name=${quote2(name)} ]`);
1388
2956
  install.send.push(INSTALL_CERTIFICATE + name);
1389
2957
  }
1390
2958
  for (const entry of existing) {
1391
2959
  if (wantedNames.has(entry.attrs.name) || renamed.has(entry.attrs.name))
1392
2960
  continue;
1393
2961
  remove.show.push(`- ${entry.attrs.name}`);
1394
- remove.send.push(`${menu} remove [ find name=${quote(entry.attrs.name)} ]`);
2962
+ remove.send.push(`${menu} remove [ find name=${quote2(entry.attrs.name)} ]`);
1395
2963
  }
1396
2964
  if (install.send.length)
1397
2965
  result.first.push(install);
@@ -1413,7 +2981,7 @@ function planOrdered(menu, wanted, existing) {
1413
2981
  const droppedChains = [...new Set(existing.map((item) => item.attrs.chain ?? ""))].filter((chain) => !keptChains.has(chain));
1414
2982
  let removeOld = `${menu} remove $${OLD_RULES}`;
1415
2983
  if (hasChains) {
1416
- const keep = [...keptChains].map((chain) => quote(chain)).join(";");
2984
+ const keep = [...keptChains].map((chain) => quote2(chain)).join(";");
1417
2985
  const inKeptChain = `[:typeof [:find $keep [${menu} get $id chain]]] != "nil"`;
1418
2986
  removeOld = `:local keep {${keep}}; :foreach id in=$${OLD_RULES} do={ :if (${inKeptChain}) do={ ${menu} remove $id } }`;
1419
2987
  }
@@ -1426,13 +2994,13 @@ function planOrdered(menu, wanted, existing) {
1426
2994
  result.first.push({ title: `${menu}, in the order of the config`, show, send });
1427
2995
  if (hasChains) {
1428
2996
  for (const chain of droppedChains) {
1429
- const selector = `[ find chain=${quote(chain)}${DYNAMIC.has(menu) ? " and !dynamic" : ""} ]`;
2997
+ const selector = `[ find chain=${quote2(chain)}${DYNAMIC.has(menu) ? " and !dynamic" : ""} ]`;
1430
2998
  result.last.push({ title: `${menu} chain ${chain}`, show: [`- chain ${chain}`], send: [`${menu} remove ${selector}`] });
1431
2999
  }
1432
3000
  }
1433
3001
  return result;
1434
3002
  }
1435
- function plan2(desired, current, options) {
3003
+ function plan3(desired, current, options) {
1436
3004
  const want = parse(desired);
1437
3005
  const have = parse(current);
1438
3006
  const owned = (command) => !(command.menu.endsWith("address-list") && want.generated.has(command.attrs.list ?? ""));
@@ -1479,7 +3047,7 @@ class Output {
1479
3047
  `;
1480
3048
  }
1481
3049
  }
1482
- function quote2(value) {
3050
+ function quote3(value) {
1483
3051
  const plain = /^[\w.:/@-]+$/.test(value);
1484
3052
  if (plain)
1485
3053
  return value;
@@ -1491,18 +3059,18 @@ function command(verb, properties) {
1491
3059
  for (const [name, value] of Object.entries(properties)) {
1492
3060
  if (value === undefined || value === false)
1493
3061
  continue;
1494
- const written = typeof value === "number" ? String(value) : quote2(value);
3062
+ const written = typeof value === "number" ? String(value) : quote3(value);
1495
3063
  words.push(`${name}=${written}`);
1496
3064
  }
1497
3065
  return words.join(" ");
1498
3066
  }
1499
3067
  var yesNo = (on) => on ? "yes" : "no";
1500
3068
  var familySuffix = (family) => family === "ipv4" ? "V4" : "V6";
1501
- var eachPort = (ports) => [ports].flat().map((port) => typeof port === "number" ? String(port) : `${port.min}-${port.max}`);
3069
+ var eachPort2 = (ports) => [ports].flat().map((port) => typeof port === "number" ? String(port) : `${port.min}-${port.max}`);
1502
3070
  function portList(ports) {
1503
3071
  if (ports === undefined)
1504
3072
  return;
1505
- return eachPort(ports).join(",");
3073
+ return eachPort2(ports).join(",");
1506
3074
  }
1507
3075
  function duration(seconds) {
1508
3076
  const units = [
@@ -1585,7 +3153,7 @@ function renderInterfaces(out, ctx) {
1585
3153
  bridge(out, ctx);
1586
3154
  ethernet(out, ctx);
1587
3155
  switchPorts(out, ctx);
1588
- lags(out, ctx);
3156
+ lags2(out, ctx);
1589
3157
  vlanInterfaces(out, ctx);
1590
3158
  tunnels(out, ctx);
1591
3159
  wireguard(out, ctx);
@@ -1594,7 +3162,7 @@ function renderInterfaces(out, ctx) {
1594
3162
  bridgeVlans(out, ctx);
1595
3163
  bridgeFilters(out, ctx);
1596
3164
  interfaceLists(out, ctx);
1597
- vrfs(out, ctx);
3165
+ vrfs2(out, ctx);
1598
3166
  }
1599
3167
  function hardwareOffload(out, ctx) {
1600
3168
  const on = yesNo(ctx.device.hardware_offload);
@@ -1669,7 +3237,7 @@ function switchPorts(out, ctx) {
1669
3237
  }));
1670
3238
  }
1671
3239
  }
1672
- function lags(out, ctx) {
3240
+ function lags2(out, ctx) {
1673
3241
  for (const [name, lag] of Object.entries(ctx.interfaces)) {
1674
3242
  if (lag.type !== "lag")
1675
3243
  continue;
@@ -1865,7 +3433,7 @@ function lldpMembers(ctx) {
1865
3433
  }
1866
3434
  return [...members];
1867
3435
  }
1868
- function vrfs(out, ctx) {
3436
+ function vrfs2(out, ctx) {
1869
3437
  for (const [vrf, settings] of Object.entries(ctx.device.vrfs ?? {})) {
1870
3438
  const members = ctx.routed().filter(({ settings: iface }) => iface.vrf === vrf).map(({ name }) => name);
1871
3439
  out.add("/ip vrf", command("add", { comment: settings.description, interfaces: members.join(","), name: vrf }));
@@ -2062,7 +3630,7 @@ function helpers(out, ctx) {
2062
3630
  }
2063
3631
  }
2064
3632
  function switchAcls(out, ctx) {
2065
- const each = (ports) => ports === undefined ? [undefined] : eachPort(ports);
3633
+ const each = (ports) => ports === undefined ? [undefined] : eachPort2(ports);
2066
3634
  for (const [port, settings] of Object.entries(ctx.ports)) {
2067
3635
  if (!settings.acl)
2068
3636
  continue;
@@ -2091,8 +3659,8 @@ function switchAcls(out, ctx) {
2091
3659
  }
2092
3660
  }
2093
3661
  }
2094
- var RPKI_GROUP = "rpki";
2095
- var REJECT_ALL = "REJECT-ALL";
3662
+ var RPKI_GROUP2 = "rpki";
3663
+ var REJECT_ALL2 = "REJECT-ALL";
2096
3664
  var ROLES = {
2097
3665
  provider: "ebgp-customer",
2098
3666
  customer: "ebgp-provider",
@@ -2101,17 +3669,17 @@ var ROLES = {
2101
3669
  "rs-client": "ebgp-rs"
2102
3670
  };
2103
3671
  function renderRouting(out, ctx) {
2104
- staticRoutes(out, ctx);
2105
- rpki(out, ctx);
3672
+ staticRoutes2(out, ctx);
3673
+ rpki2(out, ctx);
2106
3674
  if (ctx.device.routing?.bgp) {
2107
3675
  bgpInstance(out, ctx);
2108
3676
  policies(out, ctx);
2109
3677
  bgpConnections(out, ctx);
2110
3678
  }
2111
- ospf(out, ctx);
3679
+ ospf2(out, ctx);
2112
3680
  bfd(out, ctx);
2113
3681
  }
2114
- function staticRoutes(out, ctx) {
3682
+ function staticRoutes2(out, ctx) {
2115
3683
  for (const route of ctx.device.routing?.static ?? []) {
2116
3684
  const menu = familyOf2(host2(route.prefix)) === "ipv4" ? "/ip route" : "/ipv6 route";
2117
3685
  const verb = route.blackhole ? "add blackhole" : "add";
@@ -2125,12 +3693,12 @@ function staticRoutes(out, ctx) {
2125
3693
  }));
2126
3694
  }
2127
3695
  }
2128
- function rpki(out, ctx) {
3696
+ function rpki2(out, ctx) {
2129
3697
  for (const server of ctx.device.routing?.rpki ?? []) {
2130
3698
  out.add("/routing rpki", command("add", {
2131
3699
  address: server.address,
2132
3700
  comment: server.description,
2133
- group: RPKI_GROUP,
3701
+ group: RPKI_GROUP2,
2134
3702
  port: server.port
2135
3703
  }));
2136
3704
  }
@@ -2139,12 +3707,12 @@ function bgpInstance(out, ctx) {
2139
3707
  const bgp = ctx.device.routing.bgp;
2140
3708
  out.add("/routing bgp instance", command("add", { as: bgp.asn, name: "default", "router-id": ctx.device.routing?.router_id }));
2141
3709
  }
2142
- function settingsOf(ctx, neighbor) {
3710
+ function settingsOf2(ctx, neighbor) {
2143
3711
  const group = neighbor.group ? ctx.device.routing?.bgp?.groups?.[neighbor.group] : undefined;
2144
3712
  return { ...group, ...neighbor };
2145
3713
  }
2146
3714
  function familiesOf(ctx, neighbor) {
2147
- return settingsOf(ctx, neighbor).families ?? [familyOf2(neighbor.address)];
3715
+ return settingsOf2(ctx, neighbor).families ?? [familyOf2(neighbor.address)];
2148
3716
  }
2149
3717
  var chainName = (policy, family) => `${policy}-${familySuffix(family)}`;
2150
3718
  function appliesTo(ctx, rule, family) {
@@ -2224,7 +3792,7 @@ function ruleText(rule, family) {
2224
3792
  if (rule.call)
2225
3793
  parts.push(`jump ${chainName(rule.call, family)}`);
2226
3794
  if (rule.match?.rpki)
2227
- parts.push(`rpki-verify ${RPKI_GROUP}`);
3795
+ parts.push(`rpki-verify ${RPKI_GROUP2}`);
2228
3796
  const tests = conditions(rule);
2229
3797
  const steps = actions(rule, family);
2230
3798
  if (tests.length)
@@ -2249,7 +3817,7 @@ function policies(out, ctx) {
2249
3817
  };
2250
3818
  let rejectAllNeeded = false;
2251
3819
  for (const neighbor of Object.values(bgp.neighbors ?? {})) {
2252
- const settings = settingsOf(ctx, neighbor);
3820
+ const settings = settingsOf2(ctx, neighbor);
2253
3821
  for (const family of familiesOf(ctx, neighbor)) {
2254
3822
  for (const policy of [settings.import, settings.export]) {
2255
3823
  if (policy)
@@ -2280,7 +3848,7 @@ function policies(out, ctx) {
2280
3848
  if (rejectAllNeeded) {
2281
3849
  for (const family of ["ipv4", "ipv6"])
2282
3850
  out.add("/routing filter rule", command("add", {
2283
- chain: chainName(REJECT_ALL, family),
3851
+ chain: chainName(REJECT_ALL2, family),
2284
3852
  rule: "reject"
2285
3853
  }));
2286
3854
  }
@@ -2288,7 +3856,7 @@ function policies(out, ctx) {
2288
3856
  function bgpConnections(out, ctx) {
2289
3857
  const bgp = ctx.device.routing.bgp;
2290
3858
  for (const [name, neighbor] of Object.entries(bgp.neighbors ?? {})) {
2291
- const settings = settingsOf(ctx, neighbor);
3859
+ const settings = settingsOf2(ctx, neighbor);
2292
3860
  const [family] = familiesOf(ctx, neighbor);
2293
3861
  const addressFamily = familyOf2(neighbor.address);
2294
3862
  const internal = settings.remote_as === bgp.asn;
@@ -2299,7 +3867,7 @@ function bgpConnections(out, ctx) {
2299
3867
  as: bgp.asn,
2300
3868
  connect: settings.passive ? "no" : undefined,
2301
3869
  "hold-time": settings.hold_time ? duration(settings.hold_time) : undefined,
2302
- "input.filter": chainName(settings.import ?? REJECT_ALL, family),
3870
+ "input.filter": chainName(settings.import ?? REJECT_ALL2, family),
2303
3871
  instance: "default",
2304
3872
  [`input.limit-process-routes-${family}`]: settings.max_prefixes,
2305
3873
  "keepalive-time": settings.keepalive ? duration(settings.keepalive) : undefined,
@@ -2308,7 +3876,7 @@ function bgpConnections(out, ctx) {
2308
3876
  "local.role": role,
2309
3877
  multihop: settings.multihop ? "yes" : undefined,
2310
3878
  name,
2311
- "output.filter-chain": chainName(settings.export ?? REJECT_ALL, family),
3879
+ "output.filter-chain": chainName(settings.export ?? REJECT_ALL2, family),
2312
3880
  "output.network": originates ? NETWORKS[family] : undefined,
2313
3881
  "remote.address": `${neighbor.address}/${addressFamily === "ipv4" ? 32 : 128}`,
2314
3882
  "remote.as": settings.remote_as,
@@ -2317,7 +3885,7 @@ function bgpConnections(out, ctx) {
2317
3885
  }));
2318
3886
  }
2319
3887
  }
2320
- function ospf(out, ctx) {
3888
+ function ospf2(out, ctx) {
2321
3889
  const ospf = ctx.device.routing?.ospf;
2322
3890
  if (!ospf)
2323
3891
  return;
@@ -2348,7 +3916,7 @@ function ospf(out, ctx) {
2348
3916
  }
2349
3917
  function bfd(out, ctx) {
2350
3918
  const routing = ctx.device.routing;
2351
- const bgpWants = Object.values(routing?.bgp?.neighbors ?? {}).some((neighbor) => settingsOf(ctx, neighbor).bfd);
3919
+ const bgpWants = Object.values(routing?.bgp?.neighbors ?? {}).some((neighbor) => settingsOf2(ctx, neighbor).bfd);
2352
3920
  const ospfWants = Object.values(routing?.ospf?.areas ?? {}).some((area) => Object.values(area.interfaces).some((iface) => iface?.bfd));
2353
3921
  if (bgpWants || ospfWants)
2354
3922
  out.add("/routing bfd configuration", command("add", { interfaces: "all" }));
@@ -2364,16 +3932,16 @@ var SERVICES = {
2364
3932
  native: "winbox"
2365
3933
  };
2366
3934
  var USER_GROUPS = { admin: "full", operator: "write", "read-only": "read" };
2367
- var SNMP_AUTH2 = { sha1: "SHA1", sha256: "SHA1" };
2368
- var SNMP_PRIVACY = { aes128: "AES", des: "DES" };
3935
+ var SNMP_AUTH3 = { sha1: "SHA1", sha256: "SHA1" };
3936
+ var SNMP_PRIVACY2 = { aes128: "AES", des: "DES" };
2369
3937
  function renderManagement(out, ctx) {
2370
- services(out, ctx);
3938
+ services2(out, ctx);
2371
3939
  layerTwo(out, ctx);
2372
- snmp(out, ctx);
2373
- users(out, ctx);
3940
+ snmp2(out, ctx);
3941
+ users2(out, ctx);
2374
3942
  certificates(out, ctx);
2375
3943
  }
2376
- function services(out, ctx) {
3944
+ function services2(out, ctx) {
2377
3945
  const management = ctx.device.management ?? {};
2378
3946
  for (const [neutral, platform] of Object.entries(SERVICES)) {
2379
3947
  const service = management[neutral];
@@ -2405,7 +3973,7 @@ function layerTwo(out, ctx) {
2405
3973
  discovery = "all";
2406
3974
  out.add("/ip neighbor discovery-settings", command("set", { "discover-interface-list": discovery, protocol: "lldp" }));
2407
3975
  }
2408
- function snmp(out, ctx) {
3976
+ function snmp2(out, ctx) {
2409
3977
  const management = ctx.device.management ?? {};
2410
3978
  const snmp = management.snmp;
2411
3979
  if (!snmp) {
@@ -2423,15 +3991,15 @@ function snmp(out, ctx) {
2423
3991
  out.add("/snmp community", command("add", {
2424
3992
  addresses: allow,
2425
3993
  "authentication-password": marker(user.auth_password),
2426
- "authentication-protocol": SNMP_AUTH2[user.auth],
3994
+ "authentication-protocol": SNMP_AUTH3[user.auth],
2427
3995
  "encryption-password": marker(user.privacy_password),
2428
- "encryption-protocol": SNMP_PRIVACY[user.privacy],
3996
+ "encryption-protocol": SNMP_PRIVACY2[user.privacy],
2429
3997
  name,
2430
3998
  security: "private"
2431
3999
  }));
2432
4000
  }
2433
4001
  }
2434
- function users(out, ctx) {
4002
+ function users2(out, ctx) {
2435
4003
  for (const [name, user] of Object.entries(ctx.device.users ?? {})) {
2436
4004
  out.add("/user", command("add", {
2437
4005
  comment: user.description,
@@ -2526,7 +4094,7 @@ function flowExport(out, ctx) {
2526
4094
  out.add("/ip traffic-flow target", command("add", { "dst-address": collector.address, port: collector.port ?? 2055, version }));
2527
4095
  }
2528
4096
  }
2529
- function render2(device) {
4097
+ function render3(device) {
2530
4098
  const out = new Output;
2531
4099
  const ctx = new Context(device);
2532
4100
  renderInterfaces(out, ctx);
@@ -2539,8 +4107,8 @@ function render2(device) {
2539
4107
  }
2540
4108
 
2541
4109
  // src/adapters/devices/routeros/index.ts
2542
- var SUPPORTED_MAJOR = 7;
2543
- async function login(device) {
4110
+ var SUPPORTED_MAJOR2 = 7;
4111
+ async function login2(device) {
2544
4112
  const user = device.users?.[device.connection.user];
2545
4113
  if (!user)
2546
4114
  throw new Error(`${device.name}: connection user ${device.connection.user} is not one of its users`);
@@ -2551,11 +4119,11 @@ async function login(device) {
2551
4119
  password: resolve(user.password)
2552
4120
  });
2553
4121
  }
2554
- async function checkVersion(client, device) {
4122
+ async function checkVersion2(client, device) {
2555
4123
  const version = (await exec(client, ":put [/system resource get version]")).trim();
2556
4124
  const major = Number(version.split(".")[0]);
2557
- if (major !== SUPPORTED_MAJOR) {
2558
- throw new Error(`${device.name} runs RouterOS ${version}; this adapter renders for version ${SUPPORTED_MAJOR}`);
4125
+ if (major !== SUPPORTED_MAJOR2) {
4126
+ throw new Error(`${device.name} runs RouterOS ${version}; this adapter renders for version ${SUPPORTED_MAJOR2}`);
2559
4127
  }
2560
4128
  }
2561
4129
  var EXPORT_TIMESTAMP = /^# \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} by RouterOS/m;
@@ -2567,10 +4135,10 @@ var LIST_CERTIFICATES = [
2567
4135
  ':put ("add fingerprint=" . [/certificate get $c fingerprint] . " name=" . [/certificate get $c name] . " private-key=" . $key)',
2568
4136
  "}"
2569
4137
  ].join(" ");
2570
- async function read2(device) {
2571
- const client = await login(device);
4138
+ async function read3(device) {
4139
+ const client = await login2(device);
2572
4140
  try {
2573
- await checkVersion(client, device);
4141
+ await checkVersion2(client, device);
2574
4142
  const config = await exec(client, "/export", 300000);
2575
4143
  const users = await exec(client, "/user export");
2576
4144
  const certificates = (await exec(client, LIST_CERTIFICATES)).trim();
@@ -2591,7 +4159,7 @@ ${certificates}` : "";
2591
4159
  client.end();
2592
4160
  }
2593
4161
  }
2594
- function unsupported2(device) {
4162
+ function unsupported3(device) {
2595
4163
  const found = [];
2596
4164
  for (const [name, port] of Object.entries(device.ports ?? {})) {
2597
4165
  if (!port)
@@ -2665,9 +4233,9 @@ async function installCertificate(client, device, name) {
2665
4233
  }
2666
4234
  await exec(client, `/file remove [find name="${name}.crt" or name="${name}.key"]`);
2667
4235
  }
2668
- async function apply2(device, _network, changes, options) {
2669
- const client = await login(device);
2670
- await checkVersion(client, device);
4236
+ async function apply3(device, _network, changes, options) {
4237
+ const client = await login2(device);
4238
+ await checkVersion2(client, device);
2671
4239
  const commands = changes.steps.flatMap((step) => step.send.map((command) => ({ title: step.title, command })));
2672
4240
  for (const { title, command } of commands) {
2673
4241
  if (command.startsWith(INSTALL_CERTIFICATE))
@@ -2713,7 +4281,7 @@ async function apply2(device, _network, changes, options) {
2713
4281
  throw new Error("apply failed and the backup is being restored");
2714
4282
  }
2715
4283
  client.end();
2716
- const proof = await login(device).catch(() => {
4284
+ const proof = await login2(device).catch(() => {
2717
4285
  return;
2718
4286
  });
2719
4287
  if (!proof)
@@ -2723,10 +4291,10 @@ async function apply2(device, _network, changes, options) {
2723
4291
  proof.end();
2724
4292
  console.log(" logged in again, rollback disarmed");
2725
4293
  }
2726
- async function fill(device, _network, set, family, prefixes) {
4294
+ async function fill2(device, _network, set, family, prefixes) {
2727
4295
  const menu = family === "ipv4" ? "/ip firewall address-list" : "/ipv6 firewall address-list";
2728
4296
  const batchSize = 60;
2729
- const client = await login(device);
4297
+ const client = await login2(device);
2730
4298
  try {
2731
4299
  await exec(client, `${menu} remove [find list="${set}" and !dynamic]`);
2732
4300
  for (let start = 0;start < prefixes.length; start += batchSize) {
@@ -2743,16 +4311,16 @@ async function fill(device, _network, set, family, prefixes) {
2743
4311
  }
2744
4312
  var routeros = {
2745
4313
  extension: ".rsc",
2746
- unsupported: unsupported2,
2747
- render: render2,
2748
- read: read2,
2749
- plan: plan2,
2750
- apply: apply2,
2751
- fill
4314
+ unsupported: unsupported3,
4315
+ render: render3,
4316
+ read: read3,
4317
+ plan: plan3,
4318
+ apply: apply3,
4319
+ fill: fill2
2752
4320
  };
2753
4321
 
2754
4322
  // src/adapters/devices/vrp/index.ts
2755
- var DEVICE_FACTS2 = [
4323
+ var DEVICE_FACTS3 = [
2756
4324
  "!",
2757
4325
  "device ",
2758
4326
  "system resource",
@@ -2807,7 +4375,7 @@ var dialect2 = {
2807
4375
  block: /^(interface |vlan \d+$|acl number |aaa$|user-interface |ospf |ip vpn-instance |bfd$)/,
2808
4376
  indented: true,
2809
4377
  terminator: "#",
2810
- ignore: (line) => DEVICE_FACTS2.some((fact) => line.startsWith(fact)),
4378
+ ignore: (line) => DEVICE_FACTS3.some((fact) => line.startsWith(fact)),
2811
4379
  undo: undo2,
2812
4380
  leave: "quit",
2813
4381
  first: /^authentication-mode /,
@@ -2817,10 +4385,10 @@ var dialect2 = {
2817
4385
  var MANAGEMENT_ACL2 = 2000;
2818
4386
  var OSPF_PROCESS2 = 1;
2819
4387
  var FIRST_COLLECTOR = 1;
2820
- var SNMP_GROUP2 = "circuit";
4388
+ var SNMP_GROUP3 = "circuit";
2821
4389
  var USER_LEVEL = { admin: 3, operator: 2, "read-only": 1 };
2822
- var SNMP_AUTH3 = { sha1: "sha", sha256: "sha2-256" };
2823
- var SNMP_PRIVACY2 = { aes128: "aes128", des: "des56" };
4390
+ var SNMP_AUTH4 = { sha1: "sha", sha256: "sha2-256" };
4391
+ var SNMP_PRIVACY3 = { aes128: "aes128", des: "des56" };
2824
4392
  function batchIds(line) {
2825
4393
  const words = line.replace(/^vlan batch /, "").split(" ");
2826
4394
  const ids = [];
@@ -2867,7 +4435,7 @@ class Config {
2867
4435
  `;
2868
4436
  }
2869
4437
  }
2870
- function render3(device) {
4438
+ function render4(device) {
2871
4439
  const config = new Config;
2872
4440
  const vlans = device.vlans ?? {};
2873
4441
  const vlanId = (name) => vlans[name].id;
@@ -2881,14 +4449,14 @@ function render3(device) {
2881
4449
  return `Vlanif${vlanId(iface.vlan)}`;
2882
4450
  return spell(device.platform, device.model, portOrInterface);
2883
4451
  };
2884
- system(config, device);
4452
+ system2(config, device);
2885
4453
  vlanBlocks(config, device);
2886
4454
  managementAcl(config, device);
2887
- users2(config, device);
2888
- vrfs2(config, device);
4455
+ users3(config, device);
4456
+ vrfs3(config, device);
2889
4457
  for (const [name, lag] of Object.entries(interfaces)) {
2890
4458
  if (lag.type === "lag")
2891
- config.block(`interface ${nameOf(name)}`, [...lagMode(lag), ...switching2(lag, vlanId)]);
4459
+ config.block(`interface ${nameOf(name)}`, [...lagMode(lag), ...switching3(lag, vlanId)]);
2892
4460
  }
2893
4461
  for (const [name, iface] of Object.entries(interfaces)) {
2894
4462
  if (iface.type === "vlan")
@@ -2898,8 +4466,8 @@ function render3(device) {
2898
4466
  const settings = device.ports?.[port];
2899
4467
  config.block(`interface ${nameOf(port)}`, physicalPort2(device, port, settings, vlanId, lagNumbers));
2900
4468
  }
2901
- staticRoutes2(config, device, nameOf);
2902
- ospf2(config, device, nameOf);
4469
+ staticRoutes3(config, device, nameOf);
4470
+ ospf3(config, device, nameOf);
2903
4471
  if (wantsBfd(device))
2904
4472
  config.block("bfd", []);
2905
4473
  if (device.dhcp_relay && Object.keys(device.dhcp_relay).length) {
@@ -2907,12 +4475,12 @@ function render3(device) {
2907
4475
  config.separator();
2908
4476
  }
2909
4477
  flowExport2(config, device);
2910
- snmp2(config, device);
4478
+ snmp3(config, device);
2911
4479
  ssh(config, device);
2912
4480
  userInterfaces(config, device);
2913
4481
  return config.text();
2914
4482
  }
2915
- function system(config, device) {
4483
+ function system2(config, device) {
2916
4484
  config.line(`sysname ${device.name}`);
2917
4485
  config.separator();
2918
4486
  const timezone = device.system?.timezone;
@@ -2967,7 +4535,7 @@ function managementAcl(config, device) {
2967
4535
  const rules = allow.map((cidr, index) => `rule ${(index + 1) * 5} permit source ${host2(cidr)} ${wildcard(split(cidr).len)}`);
2968
4536
  config.block(`acl number ${MANAGEMENT_ACL2}`, ["description Management access", ...rules, `rule ${(allow.length + 1) * 5} deny`]);
2969
4537
  }
2970
- function users2(config, device) {
4538
+ function users3(config, device) {
2971
4539
  const lines = [];
2972
4540
  for (const [name, user] of Object.entries(device.users ?? {})) {
2973
4541
  lines.push(`local-user ${name} password irreversible-cipher ${marker(user.password)}`);
@@ -2976,7 +4544,7 @@ function users2(config, device) {
2976
4544
  }
2977
4545
  config.block("aaa", lines);
2978
4546
  }
2979
- function vrfs2(config, device) {
4547
+ function vrfs3(config, device) {
2980
4548
  for (const name of Object.keys(device.vrfs ?? {}))
2981
4549
  config.block(`ip vpn-instance ${name}`, ["ipv4-family"]);
2982
4550
  }
@@ -2986,7 +4554,7 @@ function lagMode(lag) {
2986
4554
  lines.unshift(`description ${lag.description}`);
2987
4555
  return lines;
2988
4556
  }
2989
- function switching2(settings, vlanId) {
4557
+ function switching3(settings, vlanId) {
2990
4558
  const lines = [];
2991
4559
  if (settings.access_vlan)
2992
4560
  lines.push(`port default vlan ${vlanId(settings.access_vlan)}`);
@@ -3079,7 +4647,7 @@ function physicalPort2(device, port, settings, vlanId, lagNumbers) {
3079
4647
  const lines = [];
3080
4648
  if (settings.description)
3081
4649
  lines.push(`description ${settings.description}`);
3082
- lines.push(...switching2(settings, vlanId));
4650
+ lines.push(...switching3(settings, vlanId));
3083
4651
  if (settings.lldp === false)
3084
4652
  lines.push("lldp disable");
3085
4653
  const flow = device.flow_export;
@@ -3092,7 +4660,7 @@ function physicalPort2(device, port, settings, vlanId, lagNumbers) {
3092
4660
  }
3093
4661
  return lines;
3094
4662
  }
3095
- function staticRoutes2(config, device, nameOf) {
4663
+ function staticRoutes3(config, device, nameOf) {
3096
4664
  for (const route of device.routing?.static ?? []) {
3097
4665
  const { addr, len } = split(route.prefix);
3098
4666
  const nextHop = route.blackhole ? "NULL0" : route.via ?? nameOf(route.interface);
@@ -3106,7 +4674,7 @@ function staticRoutes2(config, device, nameOf) {
3106
4674
  }
3107
4675
  config.separator();
3108
4676
  }
3109
- function ospf2(config, device, nameOf) {
4677
+ function ospf3(config, device, nameOf) {
3110
4678
  const ospf = device.routing?.ospf;
3111
4679
  if (!ospf)
3112
4680
  return;
@@ -3141,7 +4709,7 @@ function flowExport2(config, device) {
3141
4709
  });
3142
4710
  config.separator();
3143
4711
  }
3144
- function snmp2(config, device) {
4712
+ function snmp3(config, device) {
3145
4713
  const snmp = device.management?.snmp;
3146
4714
  if (!snmp)
3147
4715
  return;
@@ -3157,11 +4725,11 @@ function snmp2(config, device) {
3157
4725
  config.line(`snmp-agent community read cipher ${marker(snmp.community)}${guarded ? ` acl ${MANAGEMENT_ACL2}` : ""}`);
3158
4726
  if (snmp.users) {
3159
4727
  config.line("snmp-agent mib-view included all iso");
3160
- config.line(`snmp-agent group v3 ${SNMP_GROUP2} privacy read-view all${guarded ? ` acl ${MANAGEMENT_ACL2}` : ""}`);
4728
+ config.line(`snmp-agent group v3 ${SNMP_GROUP3} privacy read-view all${guarded ? ` acl ${MANAGEMENT_ACL2}` : ""}`);
3161
4729
  for (const [name, user] of Object.entries(snmp.users)) {
3162
- config.line(`snmp-agent usm-user v3 ${name} group ${SNMP_GROUP2}`);
3163
- config.line(`snmp-agent usm-user v3 ${name} authentication-mode ${SNMP_AUTH3[user.auth]} cipher ${marker(user.auth_password)}`);
3164
- config.line(`snmp-agent usm-user v3 ${name} privacy-mode ${SNMP_PRIVACY2[user.privacy]} cipher ${marker(user.privacy_password)}`);
4730
+ config.line(`snmp-agent usm-user v3 ${name} group ${SNMP_GROUP3}`);
4731
+ config.line(`snmp-agent usm-user v3 ${name} authentication-mode ${SNMP_AUTH4[user.auth]} cipher ${marker(user.auth_password)}`);
4732
+ config.line(`snmp-agent usm-user v3 ${name} privacy-mode ${SNMP_PRIVACY3[user.privacy]} cipher ${marker(user.privacy_password)}`);
3165
4733
  }
3166
4734
  }
3167
4735
  config.separator();
@@ -3195,7 +4763,7 @@ function userInterfaces(config, device) {
3195
4763
  config.block("user-interface vty 0 4", ["authentication-mode aaa", `protocol inbound ${device.management?.telnet ? "all" : "ssh"}`]);
3196
4764
  }
3197
4765
  var HOST_KEYS2 = ["ssh-rsa"];
3198
- async function login2(device) {
4766
+ async function login3(device) {
3199
4767
  const user = device.users?.[device.connection.user];
3200
4768
  if (!user)
3201
4769
  throw new Error(`${device.name}: connection user ${device.connection.user} is not one of its users`);
@@ -3215,8 +4783,8 @@ async function openShell(client) {
3215
4783
  await send(channel, "screen-length 0 temporary", 1e4, 1500);
3216
4784
  return channel;
3217
4785
  }
3218
- async function read3(device) {
3219
- const client = await login2(device);
4786
+ async function read4(device) {
4787
+ const client = await login3(device);
3220
4788
  try {
3221
4789
  const channel = await openShell(client);
3222
4790
  const dump = await send(channel, "display current-configuration", 120000, 3000);
@@ -3227,14 +4795,14 @@ async function read3(device) {
3227
4795
  if (start < 0)
3228
4796
  throw new Error(`${device.name} returned no configuration`);
3229
4797
  const body = lines.slice(start).filter((line) => !/^<.*>$/.test(line.trim()) && !line.startsWith("!Last configuration"));
3230
- return scrub(body.join(`
4798
+ return scrub2(body.join(`
3231
4799
  `).trim() + `
3232
4800
  `);
3233
4801
  } finally {
3234
4802
  client.end();
3235
4803
  }
3236
4804
  }
3237
- function unsupported3(device) {
4805
+ function unsupported4(device) {
3238
4806
  const found = [];
3239
4807
  for (const [name, port] of Object.entries(device.ports ?? {})) {
3240
4808
  if (!port)
@@ -3313,7 +4881,7 @@ function unsupported3(device) {
3313
4881
  }
3314
4882
  return found;
3315
4883
  }
3316
- var REFUSED2 = /^\s*Error|Unrecognized command|Incomplete command|Wrong parameter|Too many parameters/m;
4884
+ var REFUSED3 = /^\s*Error|Unrecognized command|Incomplete command|Wrong parameter|Too many parameters/m;
3317
4885
  var INTERACTIVE = /^(snmp-agent usm-user v3 \S+ (?:authentication|privacy)-mode \S+) cipher <secret:([A-Za-z0-9_./:-]+)>$/;
3318
4886
  async function sendLine(channel, line) {
3319
4887
  const interactive = INTERACTIVE.exec(line.trim());
@@ -3330,21 +4898,21 @@ async function sendLine(channel, line) {
3330
4898
  answer += await send(channel, "Y", 20000, 800);
3331
4899
  return answer;
3332
4900
  }
3333
- async function apply3(device, _network, plan) {
3334
- const client = await login2(device);
4901
+ async function apply4(device, _network, plan) {
4902
+ const client = await login3(device);
3335
4903
  const channel = await openShell(client);
3336
4904
  try {
3337
4905
  await send(channel, "system-view", 1e4, 1500);
3338
4906
  for (const step of plan.steps) {
3339
4907
  for (const line of step.send) {
3340
4908
  const answer = await sendLine(channel, line);
3341
- if (REFUSED2.test(answer))
4909
+ if (REFUSED3.test(answer))
3342
4910
  throw new Error(`${step.title}: ${line}
3343
4911
  device said: ${answer.trim().slice(0, 300)}`);
3344
4912
  }
3345
4913
  }
3346
4914
  const committed = await send(channel, "commit", 60000, 3000);
3347
- if (REFUSED2.test(committed))
4915
+ if (REFUSED3.test(committed))
3348
4916
  throw new Error(`commit refused: ${committed.trim().slice(0, 300)}`);
3349
4917
  await send(channel, "quit", 1e4);
3350
4918
  } catch (error) {
@@ -3355,7 +4923,7 @@ async function apply3(device, _network, plan) {
3355
4923
  client.end();
3356
4924
  throw error;
3357
4925
  }
3358
- const proof = await login2(device).catch(() => {
4926
+ const proof = await login3(device).catch(() => {
3359
4927
  return;
3360
4928
  });
3361
4929
  if (!proof) {
@@ -3371,7 +4939,7 @@ async function apply3(device, _network, plan) {
3371
4939
  client.end();
3372
4940
  console.log(" committed, login proved, saved");
3373
4941
  }
3374
- function plan3(desired, current) {
4942
+ function plan4(desired, current) {
3375
4943
  const groups = compare(desired, current, dialect2);
3376
4944
  vlanListChange(groups, "vlan batch ", batchIds, (ids) => [`vlan batch ${vlanBatch(ids)}`], (ids) => [`vlan batch ${vlanBatch(ids)}`]);
3377
4945
  const top = groups.find((group) => group.path.length === 0);
@@ -3393,15 +4961,15 @@ function plan3(desired, current) {
3393
4961
  }
3394
4962
  var vrp = {
3395
4963
  extension: ".cfg",
3396
- unsupported: unsupported3,
3397
- render: render3,
3398
- read: read3,
3399
- plan: plan3,
3400
- apply: apply3
4964
+ unsupported: unsupported4,
4965
+ render: render4,
4966
+ read: read4,
4967
+ plan: plan4,
4968
+ apply: apply4
3401
4969
  };
3402
4970
 
3403
4971
  // src/adapters/devices/index.ts
3404
- var adapters = { routeros, vrp, "raisecom-ros": raisecom };
4972
+ var adapters = { routeros, vrp, "raisecom-ros": raisecom, junos };
3405
4973
 
3406
4974
  // src/adapters/registries/index.ts
3407
4975
  async function ripeStat(query, family) {
@@ -3422,7 +4990,7 @@ var registries = { "ripe-stat": ripeStat };
3422
4990
  // src/adapters/registries/librenms.ts
3423
4991
  var AUTH = { sha1: "SHA", sha256: "SHA-256" };
3424
4992
  var PRIVACY = { aes128: "AES", des: "DES" };
3425
- function settingsOf2(network) {
4993
+ function settingsOf3(network) {
3426
4994
  if (!network.librenms)
3427
4995
  throw new Error("the network has no librenms settings");
3428
4996
  return {
@@ -3449,7 +5017,7 @@ function credentials(device) {
3449
5017
  return;
3450
5018
  }
3451
5019
  async function planLibreNms(network) {
3452
- const { base, headers } = settingsOf2(network);
5020
+ const { base, headers } = settingsOf3(network);
3453
5021
  const response = await fetch(`${base}/devices?type=all`, { headers, signal: AbortSignal.timeout(30000) });
3454
5022
  if (!response.ok)
3455
5023
  throw new Error(`LibreNMS answered ${response.status} to the device list`);
@@ -3463,7 +5031,7 @@ async function planLibreNms(network) {
3463
5031
  };
3464
5032
  }
3465
5033
  async function sendLibreNms(network, additions) {
3466
- const { base, headers } = settingsOf2(network);
5034
+ const { base, headers } = settingsOf3(network);
3467
5035
  for (const device of additions) {
3468
5036
  const body = { hostname: device.connection.host, display_template: device.name, ...credentials(device) };
3469
5037
  const response = await fetch(`${base}/devices`, {
@@ -3749,7 +5317,7 @@ function checkAddresses(holders) {
3749
5317
  const first = new Map;
3750
5318
  const isLoopback = (holder) => holder.device.interfaces?.[holder.name]?.type === "loopback";
3751
5319
  for (const holder of holders) {
3752
- const key = keyOf(host2(holder.address));
5320
+ const key = `${holder.vrf ?? ""} ${keyOf(host2(holder.address))}`;
3753
5321
  const earlier = first.get(key);
3754
5322
  if (!earlier) {
3755
5323
  first.set(key, holder);
@@ -3853,7 +5421,9 @@ function checkTunnels(devices, owners) {
3853
5421
  continue;
3854
5422
  const report = (message) => findings.push({ level: "error", device: end.device.name, message: `interface ${end.name}: ${message}` });
3855
5423
  const kind = end.iface.type === "gre" ? "GRE" : "VXLAN";
3856
- const mirror = ends.find((other) => other.device === far && other.iface.type === end.iface.type && same(other.iface.local, end.iface.remote) && same(other.iface.remote, end.iface.local));
5424
+ const back = ends.filter((other) => other.device === far && other.iface.type === end.iface.type && same(other.iface.local, end.iface.remote) && same(other.iface.remote, end.iface.local));
5425
+ const vniOf = (iface) => iface.type === "vxlan" ? iface.vni : undefined;
5426
+ const mirror = back.find((other) => vniOf(other.iface) === vniOf(end.iface)) ?? back[0];
3857
5427
  if (!mirror) {
3858
5428
  report(`${end.iface.remote} is ${far.name}'s, which has no ${kind} tunnel from there back to ${end.iface.local}`);
3859
5429
  continue;
@@ -3895,7 +5465,7 @@ function validate(network) {
3895
5465
  return findings;
3896
5466
  }
3897
5467
 
3898
- class Names {
5468
+ class Names2 {
3899
5469
  device;
3900
5470
  report;
3901
5471
  ports;
@@ -3922,7 +5492,7 @@ class Names {
3922
5492
  }
3923
5493
  }
3924
5494
  function checkDevice(device, report) {
3925
- const names = new Names(device, report);
5495
+ const names = new Names2(device, report);
3926
5496
  const cidr = (where, value) => {
3927
5497
  if (!isCidr2(value))
3928
5498
  report(`${where}: '${value}' is not an address with a length`);
@@ -3966,9 +5536,10 @@ function checkAddresses2(device, report, cidr, ip) {
3966
5536
  if (typeof entry !== "string")
3967
5537
  ip(where, entry.peer);
3968
5538
  const bare = address.split("/")[0];
3969
- if (holderOf.has(bare))
3970
- report(`${bare} is on both ${holderOf.get(bare)} and ${where}`);
3971
- holderOf.set(bare, where);
5539
+ const key = `${settings?.vrf ?? ""} ${bare}`;
5540
+ if (holderOf.has(key))
5541
+ report(`${bare} is on both ${holderOf.get(key)} and ${where}`);
5542
+ holderOf.set(key, where);
3972
5543
  }
3973
5544
  for (const group of settings?.vrrp ?? []) {
3974
5545
  ip(`${where} VRRP ${group.id}`, group.address);
@@ -4824,7 +6395,7 @@ async function studioCommand() {
4824
6395
  const port = Number(flagValue("port") ?? 4600);
4825
6396
  if (!Number.isInteger(port) || port < 1 || port > 65535)
4826
6397
  fail("--port takes a number from 1 to 65535");
4827
- const { isLoopback, studio } = await import("../chunks/server-ckdy25w2.js");
6398
+ const { isLoopback, studio } = await import("../chunks/server-n2q4tzy4.js");
4828
6399
  const host = flagValue("host") ?? "127.0.0.1";
4829
6400
  const allowHosts = (flagValue("allow-host") ?? "").split(",").filter(Boolean);
4830
6401
  if (!isLoopback(host) && !allowHosts.length) {
@@ -4850,9 +6421,9 @@ var commands = {
4850
6421
  graph: graphCommand,
4851
6422
  studio: studioCommand
4852
6423
  };
4853
- var run = commands[command2];
4854
- if (run)
4855
- await run();
6424
+ var run2 = commands[command2];
6425
+ if (run2)
6426
+ await run2();
4856
6427
  else
4857
6428
  fail(`no command ${command2}
4858
6429