FABRIC UP
Static documentation snapshot of a live operations portal. The FABRIC badge above reads a local backend at 127.0.0.1:5757, so on the public web it shows FABRIC ? — that is expected (there is no public backend). Device configs, topology and diagrams below are all live. Want the real running fabric on your own machine? ↳ Build your own Docker lab (step-by-step)  ·  repo ↗

Multi-Vendor Dual-Stack EVPN-VXLAN Fabric

15
Total Nodes
3
Spine RRs
6
Leaf VTEPs
6
Dual-Homed Hosts
3
Vendors
36+18
BGP IPv4+IPv6
IPv6
Dual-Stack
3
VLANs / VNIs
30/30
Ping Matrix

SRL Nokia SR Linux

spine1 (RR), leaf2 (VTEP rack-1), leaf5 (VTEP rack-3)
SR Linux v24.10.3 — Full EVPN-MH with ESI-LAG, IRB anycast gateway, symmetric IRB routing via ip-vrf

cEOS Arista cEOS

spine2 (RR), leaf1 (VTEP rack-1), leaf4 (VTEP rack-2)
cEOS 4.33.1F — EVPN multihoming with Port-Channel ESI, virtual-router anycast MAC, VXLAN L3 VNI

FRR FRRouting

spine3 (RR), leaf3 (VTEP rack-2), leaf6 (VTEP rack-3)
FRR 8.4 — EVPN-MH via es-id/es-sys-mac, Linux bridge VXLAN. L3 VNI limited (no kernel VRF in container)

Architecture Highlights

eBGP Underlay

Pure eBGP underlay (RFC 7938). Each leaf has unique ASN (65001-65006), spines share AS 65100. Loopbacks redistributed via route-map. No OSPF/IS-IS IGP.

eBGP EVPN Overlay

EVPN overlay runs as eBGP with multihop between leaf loopbacks and spine route-reflectors. Per-leaf AS numbers preserved — no AS swap tricks needed.

ESI-LAG Multihoming

Every host dual-homed to a leaf pair via LACP bond0. ESI (Ethernet Segment Identifier) ensures active-active forwarding with sub-second failover via EVPN Type-1/Type-4.

IRB Anycast Gateway

Symmetric IRB with shared anycast MAC (00:00:5E:00:53:01). L3 VNI 50001 in TENANT-A VRF. All hosts see the same gateway MAC regardless of which leaf they hit.

SNMP Monitoring

All 9 network nodes send SNMP traps to 172.20.20.1:1162. Community: GESH-DC1-RO. SRL/cEOS use native SNMP; FRR uses external snmpd + AgentX.

Inter-VLAN Routing

30/30 host-to-host paths verified. VLAN 10 (10.10.10.0/24), VLAN 20 (10.10.20.0/24), VLAN 30 (10.10.30.0/24). Full mesh via symmetric IRB over VXLAN.

Fabric Topology

                        ┌─────────────────────────────────────────────────────────┐
                        │                    SPINE LAYER (AS 65100)                │
                        │                   EVPN Route Reflectors                  │
                        │                                                         │
                        │   spine1              spine2              spine3     │
                        │   Nokia SRL           Arista cEOS          FRR          │
                        │   10.255.0.1          10.255.0.2           10.255.0.3   │
                        │   172.20.20.11        172.20.20.12         172.20.20.13 │
                        └───────┬──────────────────┬─────────────────┬────────────┘
                    ┌───────────┼──────────────────┼─────────────────┼───────────┐
         ┌──────────┼───────────┼──────┬───────────┼──────┬──────────┼───────────┼──────────┐
         │          │           │      │           │      │          │           │          │
   ┌─────┴────┐ ┌───┴─────┐ ┌──┴──────┴──┐ ┌─────┴────┐ ┌┴─────────┴─┐ ┌──────┴────────┐ │
   │  leaf1   │ │ leaf2   │ │   leaf3   │ │  leaf4   │ │   leaf5    │ │    leaf6     │ │
   │  cEOS    │ │ SRL     │ │   FRR     │ │  cEOS    │ │   SRL      │ │    FRR       │ │
   │  AS65001 │ │ AS65002 │ │  AS65003  │ │  AS65004 │ │  AS65005   │ │   AS65006    │ │
   │ VNI10010 │ │VNI10010 │ │ VNI10020  │ │VNI10020  │ │ VNI10030   │ │  VNI10030    │ │
   └──┬───┬───┘ └──┬───┬──┘ └──┬────┬──┘ └──┬───┬───┘ └──┬────┬───┘ └──┬─────┬───┘ │
      │   │        │   │       │    │       │   │        │    │        │     │      │
      │   └────────┤   ├───────┘    └───────┤   ├────────┘    └────────┤     │      │
      │    ESI-LAG │   │ ESI-LAG     ESI-LAG│   │  ESI-LAG     ESI-LAG│     │      │
   ┌──┴──┐     ┌───┴───┴──┐     ┌───┴───┴──┐     ┌───┴──┐      ┌────┴─────┴──┐   │
   │host1│     │  host2   │     │  host3  │     │host4│      │  host5     │   │
   │bond0│     │  bond0   │     │  bond0   │     │bond0 │      │  bond0      │   │
   │LACP │     │  LACP    │     │  LACP    │     │LACP  │      │  LACP       │   │
   │.10.1│     │  .10.2   │     │  .20.1   │     │.20.2 │      │  .30.1      │   │
   └─────┘     └──────────┘     └──────────┘     └──────┘      └─────────────┘   │
                                                                                  │
   ┌──────────────────────────────────────────────────────────────────────────────┘
   │  host6 — bond0 active-backup, primary=eth2 (leaf5 SRL) for L3 VNI routing
   │  10.10.30.2/24
   └──────────────────────────────────────────────────────────────────────────────
  

IP Addressing

DeviceRoleVendorASNLoopbackVTEPMgmt
spine1Spine RRSRL6510010.255.0.1172.20.20.11
spine2Spine RRcEOS6510010.255.0.2172.20.20.12
spine3Spine RRFRR6510010.255.0.3172.20.20.13
leaf1Leaf VTEPcEOS6500110.255.1.110.255.1.1172.20.20.21
leaf2Leaf VTEPSRL6500210.255.1.210.255.100.2172.20.20.22
leaf3Leaf VTEPFRR6500310.255.1.310.255.100.3172.20.20.23
leaf4Leaf VTEPcEOS6500410.255.1.410.255.1.4172.20.20.24
leaf5Leaf VTEPSRL6500510.255.1.510.255.100.5172.20.20.25
leaf6Leaf VTEPFRR6500610.255.1.610.255.100.6172.20.20.26

IPv6 Addressing (Dual-Stack)

Loopbacks: fd00:dc1::X/128 — P2P links: fd00:dc1:SPINE:LEAF::/127 (::0 = spine side, ::1 = leaf side)

DeviceIPv6 LoopbackP2P to spine1P2P to spine2P2P to spine3
spine1fd00:dc1::1/128
spine2fd00:dc1::2/128
spine3fd00:dc1::3/128
leaf1fd00:dc1::11/128fd00:dc1:1:1::1fd00:dc1:2:1::1fd00:dc1:3:1::1
leaf2fd00:dc1::12/128fd00:dc1:1:2::1fd00:dc1:2:2::1fd00:dc1:3:2::1
leaf3fd00:dc1::13/128fd00:dc1:1:3::1fd00:dc1:2:3::1fd00:dc1:3:3::1
leaf4fd00:dc1::14/128fd00:dc1:1:4::1fd00:dc1:2:4::1fd00:dc1:3:4::1
leaf5fd00:dc1::15/128fd00:dc1:1:5::1fd00:dc1:2:5::1fd00:dc1:3:5::1
leaf6fd00:dc1::16/128fd00:dc1:1:6::1fd00:dc1:2:6::1fd00:dc1:3:6::1

VLAN / VNI / Subnet Mapping

VLANL2 VNISubnetAnycast GWMACLeaf PairHosts
101001010.10.10.0/2410.10.10.25400:00:5E:00:53:01leaf1 (cEOS) + leaf2 (SRL)host1, host2
201002010.10.20.0/2410.10.20.25400:00:5E:00:53:01leaf3 (FRR) + leaf4 (cEOS)host3, host4
301003010.10.30.0/2410.10.30.25400:00:5E:00:53:01leaf5 (SRL) + leaf6 (FRR)host5, host6
L3 VNI50001TENANT-A VRF — symmetric IRB inter-VLAN routing

ESI-LAG Segments

HostBond ModePrimary LeafPartner LeafESI
host1802.3ad LACPleaf1 (cEOS)leaf2 (SRL)00:11:11:11:11:11:11:11:11:11
host2802.3ad LACPleaf2 (SRL)leaf1 (cEOS)00:11:11:11:11:11:11:11:22:22
host3802.3ad LACPleaf3 (FRR)leaf4 (cEOS)00:22:22:22:22:22:22:22:11:11
host4802.3ad LACPleaf4 (cEOS)leaf3 (FRR)00:22:22:22:22:22:22:22:22:22
host5802.3ad LACPleaf5 (SRL)leaf6 (FRR)00:33:33:33:33:33:33:33:11:11
host6active-backupleaf5 (SRL) *leaf6 (FRR)00:33:33:33:33:33:33:33:22:22

* host6 uses active-backup with leaf5 (SRL) as primary because leaf6 FRR lacks kernel VRF for L3 VNI decapsulation

Network Diagrams

Layer 1 — Physical cabling. Every spine-leaf veth link with interface names on both ends. All links 9194 MTU (cEOS max). Spine ports e1-1..6 / et1..6 / eth3..8; leaf uplinks et1-3 / e1-1..3 / eth1-3 — except leaf4 (cEOS), whose spine uplinks are et2/et3/et4 (et1 unused, et5/et6 = host legs).
flowchart TB
  subgraph SPINES["SPINE LAYER · AS 65100"]
    S1["spine1 · Nokia SRL
e1-1..e1-6"] S2["spine2 · Arista cEOS
et1..et6"] S3["spine3 · FRR
eth3..eth8"] end subgraph LEAFS["LEAF LAYER · VTEPs"] L1["leaf1 cEOS
et1/et2/et3"] L2["leaf2 SRL
e1-1/e1-2/e1-3"] L3["leaf3 FRR
eth1/eth2/eth3"] L4["leaf4 cEOS
et2/et3/et4"] L5["leaf5 SRL
e1-1/e1-2/e1-3"] L6["leaf6 FRR
eth1/eth2/eth3"] end S1 ---|e1-1 : et1| L1 S1 ---|e1-2 : e1-1| L2 S1 ---|e1-3 : eth1| L3 S1 ---|e1-4 : et2| L4 S1 ---|e1-5 : e1-1| L5 S1 ---|e1-6 : eth1| L6 S2 ---|et1 : et2| L1 S2 ---|et2 : e1-2| L2 S2 ---|et3 : eth2| L3 S2 ---|et4 : et3| L4 S2 ---|et5 : e1-2| L5 S2 ---|et6 : eth2| L6 S3 ---|eth3 : et3| L1 S3 ---|eth4 : e1-3| L2 S3 ---|eth5 : eth3| L3 S3 ---|eth6 : et4| L4 S3 ---|eth7 : e1-3| L5 S3 ---|eth8 : eth3| L6 L1 -.->|et4/et5 LACP| H12["host1+host2
bond0 (eth1+eth2)"] L2 -.->|e1-4/e1-5 LACP| H12 L3 -.->|eth4/eth5 LACP| H34["host3+host4
bond0"] L4 -.->|et5/et6 LACP| H34 L5 -.->|e1-4/e1-5 LACP| H56["host5+host6
bond0"] L6 -.->|eth4/eth5| H56
Layer 3 — eBGP underlay addressing. P2P /31 IPv4 + /127 IPv6 (fd00:dc1:spine:leaf::/127). Loopbacks /32 + /128. Each leaf has a unique ASN; spines share AS 65100. Dual-stack.
flowchart LR
  S1["spine1 SRL · AS65100
lo 10.255.0.1 · fd00:dc1::1"] L1["leaf1 cEOS · AS65001
lo 10.255.1.1 · fd00:dc1::11"] L2["leaf2 SRL · AS65002
lo 10.255.1.2 · fd00:dc1::12"] L3["leaf3 FRR · AS65003
lo 10.255.1.3 · fd00:dc1::13"] S1 ---|"10.0.1.0/31 · fd00:dc1:1:1::/127"| L1 S1 ---|"10.0.1.2/31 · fd00:dc1:1:2::/127"| L2 S1 ---|"10.0.1.4/31 · fd00:dc1:1:3::/127"| L3 S2["spine2 cEOS · AS65100
lo 10.255.0.2 · fd00:dc1::2"] S2 ---|"10.0.2.0/31 · fd00:dc1:2:1::/127"| L1 S2 ---|"10.0.2.2/31 · fd00:dc1:2:2::/127"| L2 S2 ---|"10.0.2.4/31 · fd00:dc1:2:3::/127"| L3 S3["spine3 FRR · AS65100
lo 10.255.0.3 · fd00:dc1::3"] S3 ---|"10.0.3.0/31 · fd00:dc1:3:1::/127"| L1 S3 ---|"10.0.3.2/31 · fd00:dc1:3:2::/127"| L2 S3 ---|"10.0.3.4/31 · fd00:dc1:3:3::/127"| L3
Leaf P2P octets: spine→leafN uses 10.0.{spine}.{(leaf-1)*2}/31 (spine side, even) ↔ +1 (leaf side, odd). leaf4–6 follow the same pattern on 10.0.{1,2,3}.{6,8,10}.
BGP control plane. Solid = eBGP underlay (IPv4/IPv6 unicast, per-leaf ASN ↔ AS65100). Dashed = eBGP EVPN overlay (leaf loopback ↔ spine loopback, multihop, spines are route-reflector-clients/transit).
flowchart TB
  S1(["spine1 RR
AS65100"]) S2(["spine2 RR
AS65100"]) S3(["spine3 RR
AS65100"]) L1["leaf1 AS65001"] L2["leaf2 AS65002"] L3["leaf3 AS65003"] L4["leaf4 AS65004"] L5["leaf5 AS65005"] L6["leaf6 AS65006"] S1 --- L1 & L2 & L3 & L4 & L5 & L6 S2 --- L1 & L2 & L3 & L4 & L5 & L6 S3 --- L1 & L2 & L3 & L4 & L5 & L6 S1 -.EVPN.- L1 & L2 & L3 & L4 & L5 & L6 S2 -.EVPN.- L1 & L2 & L3 & L4 & L5 & L6 S3 -.EVPN.- L1 & L2 & L3 & L4 & L5 & L6
18 underlay + 18 EVPN overlay sessions. A session reaches Established once OPEN/KEEPALIVE are negotiated. BFD 100ms×3 = 300ms failover on FRR/SRL.
EVPN-VXLAN data plane. L2 VNIs bridge each VLAN across the fabric; L3 VNI 50001 (TENANT-A VRF) does symmetric IRB inter-VLAN routing. Anycast gateway MAC 00:00:5E:00:53:01 shared on all leafs.
flowchart TB
  subgraph VNI10["VLAN 10 · VNI 10010 · 10.10.10.0/24"]
    direction LR
    H1["host1
10.10.10.1"] --- P1["leaf1+leaf2
ESI 00:11:..:11:11"] H2["host2
10.10.10.2"] --- P1 end subgraph VNI20["VLAN 20 · VNI 10020 · 10.10.20.0/24"] direction LR H3["host3
10.10.20.1"] --- P2["leaf3+leaf4
ESI 00:22:..:22:22"] H4["host4
10.10.20.2"] --- P2 end subgraph VNI30["VLAN 30 · VNI 10030 · 10.10.30.0/24"] direction LR H5["host5
10.10.30.1"] --- P3["leaf5+leaf6
ESI 00:33:..:33:33"] H6["host6
10.10.30.2"] --- P3 end P1 --- VRF["TENANT-A VRF
L3 VNI 50001
anycast GW 00:00:5E:00:53:01"] P2 --- VRF P3 --- VRF
Intra-VLAN = L2 VXLAN bridging. Inter-VLAN = ingress leaf routes into L3 VNI 50001, egress leaf decaps and delivers locally (symmetric IRB). 30/30 host pings verified.
Logical view — racks, VLANs, gateways. 3 racks, each a dual-homed leaf pair serving one VLAN. All hosts share the anycast default gateway .254 in their subnet.
flowchart TB
  subgraph R1["RACK 1 · VLAN 10"]
    R1GW["GW 10.10.10.254
leaf1(cEOS)+leaf2(SRL)"] R1H["host1 .1 · host2 .2"] R1H --- R1GW end subgraph R2["RACK 2 · VLAN 20"] R2GW["GW 10.10.20.254
leaf3(FRR)+leaf4(cEOS)"] R2H["host3 .1 · host4 .2"] R2H --- R2GW end subgraph R3["RACK 3 · VLAN 30"] R3GW["GW 10.10.30.254
leaf5(SRL)+leaf6(FRR)"] R3H["host5 .1 · host6 .2"] R3H --- R3GW end R1GW --- T["TENANT-A VRF
VNI 50001
inter-VLAN routing"] R2GW --- T R3GW --- T
DCN Lab — multi-region FRR backbone. 5 core routers (full-mesh-ish) + 5 edge/dist devices, hub-and-spoke per region. eBGP between regions, mgmt 10.200.0.0/24. SNMP traps to 10.200.0.1:1162.
flowchart TB
  subgraph CORE["CORE · multi-region eBGP"]
    C1["de-fra-core-01
AS65001 · .11"] C2["de-fra-core-02
AS65002 · .12"] C3["uk-lon-core-01
AS65003 · .13"] C4["nl-ams-core-01
AS65004 · .14"] C5["us-nyc-core-01
AS65005 · .15"] end C1 --- C2 & C3 & C4 C2 --- C4 & C5 C3 --- C4 & C5 C1 --- C5 C1 --- E1["de-fra-edge-01
AS65006 · .21"] C1 --- D1["de-fra-dist-01
AS65009 · .33"] C2 --- E1 & D1 C3 --- E2["uk-lon-edge-01
AS65007 · .22"] C3 --- DI2["uk-lon-dist-01
AS65010 · .31"] C4 --- E3["nl-ams-edge-01
AS65008 · .23"] C5 --- E2 D1 --- E1

Interactive Fabric Diagram

Source: docs/fabric-diagram.html

Device Configurations

Spines

SRL spine1-srl.cfg

AS 65100 • Route Reflector • eBGP underlay + EVPN overlay

cEOS spine2-ceos.cfg

AS 65100 • Route Reflector • SNMP traps enabled

FRR spine3-frr.conf

AS 65100 • eBGP EVPN overlay (per-leaf AS, no RR-client)

Leafs

cEOSV10 leaf1-ceos.cfg

AS 65001 • VTEP • ESI-LAG Port-Channel • IRB Vlan10

SRLV10 leaf2-srl.cfg

AS 65002 • VTEP • LAG + ethernet-segment • mac-vrf-10 + ip-vrf

FRRV20 leaf3-frr.conf

AS 65003 • VTEP • evpn mh es-id • Linux bridge VXLAN

cEOSV20 leaf4-ceos.cfg

AS 65004 • VTEP • ESI-LAG Port-Channel • IRB Vlan20

SRLV30 leaf5-srl.cfg

AS 65005 • VTEP • LAG + ethernet-segment • mac-vrf-30 + ip-vrf

FRRV30 leaf6-frr.conf

AS 65006 • VTEP • evpn mh es-id • Linux bridge VXLAN

Topology File

clos-evpn.clab.yml

Containerlab topology • 15 nodes • 30 links • ESI-LAG host bonding

Connectivity Matrix (30/30 Verified)

host1
V10
host2
V10
host3
V20
host4
V20
host5
V30
host6
V30
host1OKOKOKOKOK
host2OKOKOKOKOK
host3OKOKOKOKOK
host4OKOKOKOKOK
host5OKOKOKOKOK
host6OKOKOKOKOK

IPv6 Underlay Reachability

All 9 loopbacks (fd00:dc1::1 through fd00:dc1::16) are reachable over the IPv6 underlay. Each spine-leaf link carries both an IPv4 /31 and an IPv6 /127. Dedicated IPv6 BGP neighbors exchange IPv6 unicast routes independently of the IPv4 sessions. Verify with: ping6 -c1 fd00:dc1::1 from any leaf.

Protocol Stack

LayerProtocolDetails
UnderlayeBGP IPv4 + IPv6 dual-stack18 IPv4 sessions + 18 IPv6 sessions (3 spines × 6 leafs). Per-leaf ASN. Loopback redistribution via route-map EXPORT_LO. IPv6 uses dedicated native neighbors on global P2P addresses (fd00:dc1::/48)
OverlayeBGP EVPN18 sessions (leaf loopbacks → spine RRs via multihop). Type-1 AD, Type-2 MAC/IP, Type-3 IMET, Type-4 ES, Type-5 Prefix
Data PlaneVXLANL2 VNIs: 10010 (VLAN10), 10020 (VLAN20), 10030 (VLAN30). L3 VNI: 50001 (TENANT-A)
MultihomingEVPN-MH / ESI-LAG6 Ethernet Segments, all-active forwarding. LACP (802.3ad) or active-backup bonds
L3 GatewayAnycast IRBVirtual MAC 00:00:5E:00:53:01 shared across leaf pairs. Symmetric IRB via L3 VNI 50001
MonitoringSNMPv2cCommunity GESH-DC1-RO, traps to 172.20.20.1:1162

Multi-Vendor Driver Abstraction (src/drivers/)

Hybrid Architecture

Deterministic per-vendor drivers handle data collection (no LLM guesses CLI syntax). The LLM only does what it's good at — root-cause synthesis on structured/raw output. This guarantees a predictable data frame before it ever reaches the analysis layer.

Dual Output

Every driver method returns a DriverResult with .normalized (dict → Grafana/InfluxDB metrics) AND .raw (text → LLM context). Normalize for machines, raw for the model.

Single Source of Truth

Command tables + parsers live once in drivers/. Both health.py (dashboard) and clab_collector.py (telemetry) consume them — no more drift between the two.

                    ┌──────────────────────────────────────────────┐
                    │         get_driver(vendor, transport)        │
                    │              factory.py + aliases            │
                    └───────────────────┬──────────────────────────┘
                                        ▼
                    ┌──────────────────────────────────────────────┐
                    │       BaseNetworkDriver (ABC) — base.py        │
                    │  get_bgp_summary() get_interface_counters()  │
                    │  get_health()  run_command()  → DriverResult  │
                    │       (.normalized dict  +  .raw text)       │
                    └───────┬──────────────┬──────────────┬────────┘
            ┌───────────────┘              │              └───────────────┐
            ▼                              ▼                              ▼
   ┌────────────────┐          ┌────────────────┐          ┌────────────────┐
   │  FRRDriver     │          │  EOSDriver     │          │  SRLDriver     │
   │  _parse()      │          │  _parse()      │          │  _parse()      │
   └───────┬────────┘          └───────┬────────┘          └───────┬────────┘
           └────────────────────────────┼────────────────────────────┘
                                        ▼
                    ┌──────────────────────────────────────────────┐
                    │    Transport (protocol) — transport.py       │
                    │  DockerExecTransport  → vtysh / Cli / sr_cli │
                    │  SSHRunnerTransport   → wraps run_command()  │
                    │  ScrapliTransport     → (planned, SSH NOS)   │
                    └──────────────────────────────────────────────┘

   parsers.py: parse_bgp / ospf / interfaces / interface_counters / routes / version
               vendor-tagged · soft-fail (never raise) · fixed normalized shapes
  

Driver Methods

MethodReturnsNormalized shape
get_bgp_summary()DriverResult{peers[], established, total}
get_ospf_neighbors()DriverResult{neighbors[], full, total}
get_interface_status()DriverResult{list[], up, total}
get_interface_counters()DriverResult{interfaces[{in_octets, out_octets, in_packets, ...}]}
get_routes()DriverResult{total, by_protocol{}}
get_health()dict{meta, version, bgp, ospf, interfaces, routes}
run_command(cmd)DriverResult{} (raw passthrough)

Per-Vendor CLI Translation

VendorDriverTransport argvOutput format
SRL nokia-srlSRLDriversr_cli -d "<cmd>"flat tables (text parse)
cEOS arista-eosEOSDriverCli -p 15 -c "<cmd> | json"JSON
FRR frrFRRDrivervtysh -c "<cmd> json"JSON
JUNOS junosJunosDrivercli -c "<cmd> | display json"JSON (deeply nested [{data}])
IOS-XR cisco-iosxrIOSXRDriver"<cmd> | json" → text fallbackCisco text tables (JSON 7.x best-effort)

Why This Design

DecisionChoiceRationale
CollectionDeterministic driversLLM guessing CLI syntax or touching devices is an operational liability. Drivers enforce predictable commands.
AnalysisLLM on raw + normalizedModel does pattern-matching / root-cause synthesis on a known data frame — what it's actually good at.
AbstractionABC class pattern (not Jinja2)Jinja2 is for config patches. OO driver classes are cleaner for operational retrieval — drop in a new vendor class without touching core routing.
TransportInjected (not imported)Drivers never import flask/paramiko/scrapli at module top — collector runs on a lab host without them. Transport is a constructor arg.
New vendor~24-line driver classAdd JunosDriver/IOSXRDriver: set vendor + commands, implement _parse(). Factory + registry pick it up.
Failure modelSoft-fail, never raiseParsers return fixed empty shapes on bad input. One wedged command never gates the whole snapshot.

61/61 driver tests pass. Wired into health.py (dashboard command tables) and clab_collector.py (telemetry probes + interface counters → InfluxDB).

Quick Commands

Portal Service (launchd)

What runs this page This portal is served by a launchd agent com.geshlab.portal
(RunAtLoad + KeepAlive) bound to http://127.0.0.1:8099.
Script: containerlab-multivendor/scripts/start_portal.sh  ·  Log: /tmp/geshlab_portal.log
Status launchctl list | grep geshlab.portal
Restart launchctl bootout gui/$(id -u)/com.geshlab.portal
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.geshlab.portal.plist
Tail log tail -f /tmp/geshlab_portal.log

Fabric Lifecycle

Deploy (from macOS) LABPATH=~/02_Projects/.../containerlab-multivendor
docker run --rm --privileged --network host --pid host \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /run/netns:/run/netns -v "$LABPATH:$LABPATH" \
  -w "$LABPATH/topologies" --entrypoint /usr/bin/containerlab \
  ghcr.io/srl-labs/clab:latest deploy -t clos-evpn.clab.yml
Destroy ... ghcr.io/srl-labs/clab:latest destroy -t clos-evpn.clab.yml --cleanup
Check all containers docker ps --filter "name=clab-clos" --format "table {{.Names}}\t{{.Status}}"

BGP Verification

SRL (spine1, leaf2, leaf5) docker exec clab-clos-evpn-spine1 bash -c 'sr_cli "show network-instance default protocols bgp neighbor"'
cEOS (spine2, leaf1, leaf4) docker exec clab-clos-evpn-spine2 Cli -p 15 -c "show bgp evpn summary"
FRR (spine3, leaf3, leaf6) docker exec clab-clos-evpn-spine3 vtysh -c "show bgp summary"

EVPN Verification

SRL EVPN routes docker exec clab-clos-evpn-leaf2 bash -c 'sr_cli "show network-instance mac-vrf-10 bridge-table mac-table all"'
cEOS EVPN routes docker exec clab-clos-evpn-leaf1 Cli -p 15 -c "show bgp evpn"
cEOS VRF routes (inter-VLAN) docker exec clab-clos-evpn-leaf1 Cli -p 15 -c "show ip route vrf TENANT-A"

Host Verification

Check bond status docker exec clab-clos-evpn-host1 cat /proc/net/bonding/bond0
Ping matrix (all 30 paths) for s in 1 2 3 4 5 6; do for d in 1 2 3 4 5 6; do [ $s -ne $d ] && echo -n "h${s}→h${d}: " && docker exec clab-clos-evpn-host$s ping -c1 -W1 10.10.$((( (d-1)/2 *10+10 ))).$(( (d-1)%2+1 )) 2>&1|grep -oE "[0-9]+ received"; done; done

Documentation

Architecture Diagram

Interactive visual architecture of the multi-vendor DC fabric with protocol overlays and node details.

Open architecture.html →

Fabric Diagram

Live topology view with spine-leaf connections, SNMP agents, and management IPs.

Open fabric-diagram.html →

IP Plan

Complete IP addressing plan including loopbacks, P2P links, management, and VXLAN VTEPs.

Open IP_PLAN.md →

Design Decisions

DecisionChoiceRationale
UnderlayeBGP (not OSPF/IS-IS)RFC 7938 — simpler ops, per-leaf failure domains, no IGP LSA floods
OverlayeBGP EVPN (not iBGP)Each leaf keeps its own AS for EVPN. Avoids AS-swap hacks needed for iBGP on FRR 8.4
IRB modelSymmetric IRBSingle L3 VNI (50001) for all inter-VLAN traffic. Scales to N VLANs without N² tunnels
Anycast MAC00:00:5E:00:53:01Single shared MAC across all leafs — hosts see consistent gateway regardless of active leaf
host6 bondactive-backup (not LACP)FRR 8.4 container lacks kernel VRF module — L3 VNI decap fails. Force traffic to SRL leaf5
SRL LAG typestatic (not LACP)multitool host containers don't run lldpad/lacpd — LACP negotiation times out
clab deploy--pid hostDocker Desktop macOS lacks /run/netns. --pid host gives clab container access to LinuxKit VM PID namespace

Known Limitations

IssueImpactWorkaround
FRR container no kernel VRFL3 VNI 50001 can't be decapsulated on FRR leafshost6 bond primary set to SRL leaf5; FRR handles L2 only
SRL startup-config not auto-loadingSRL nodes boot blank — need manual sr_cli pushPush configs via sr_cli heredoc after deploy
cEOS interface numberingclab maps et1/et2/et3 which may not match config Ethernet1/2/3Verify with show ip interface brief after deploy
Docker Desktop netnsclab deploy fails without --pid hostAlways use --pid host flag with clab Docker image