Feature
Ze Features
Ze is an open-source configuration and protocol engine written in Go. The network operating system built on it runs on any Linux or as a gokrazy appliance.
Status values: Supported means implemented and covered in the normal release evidence for this pre-release; Experimental means implemented but still needs deployment evidence or hardening before production claims; Partial means a named subset is not implemented or not proven; Stub-backed means external dependency evidence currently comes from a stub harness; Rejected means unsupported by design; Future means planned but not shipped.
| Feature | Status | Description |
|---|---|---|
| BGP Protocol | Supported | 23 address families, 14 capabilities, 15 path attributes. The whole BGP subsystem is compile-out-able with the ze_bgp build tag: make ze-build and make ze-appliance-build include it (default-on in ZE_FEATURES), while ze-stripped and bare ze_core builds drop internal/component/bgp entirely -- engine, wire codec, RIB, and every BGP plugin -- plus flowspec-firewall, which translates BGP-delivered flowspec routes and is meaningless without it. Both composition roots are gated (the generated all_ze_bgp.go and the CLI root), and a bgp {} config block is rejected as an unknown field rather than silently ignored. A BGP-less daemon still runs OSPF, IS-IS, static routes, the FIB, MRT recording, and flow export: the route-action vocabulary, the BGP message-type codes, and the best-path-change event contract those consumers share live in always-on internal/core/bgp/* leaves, and the three places always-on code used to call into the engine (ze config dump/diff/validate resolution, the graceful-restart marker, the RIB dump provider) are inversion-of-control seams the engine fills from its own init. ze-chaos and ze-perf drive an in-process reactor and force ze_bgp on. Within a ze_bgp build the BMP monitoring plugin (RFC 7854/8671/9069) is independently compile-out-able with the ze_bmp build tag: it is a dependent gate -- BMP imports the engine, so all_ze_bmp.go is generated //go:build ze_bgp && ze_bmp and a ze_bmp-without-ze_bgp build links neither BMP nor the engine -- letting a BGP daemon ship without the BMP receiver/sender. . cmd/ze/dispatch_bgp.go |
| Configuration | ∿ | YANG-modeled config with prefix limits, update groups, session resilience, duplicate list-key rejection, side-effect-free in-process plugin verifiers for static/API/CLI validation, and transactional commits using active/candidate/rollback pointers. Live external plugin OnConfigVerify callbacks run only in daemon reload/commit transactions. |
| Deactivate / Activate | Supported | Junos-style inactive: prefix on any node (leaf, container, list entry, leaf-list value); kept in file, skipped at apply. CLI: ze config deactivate/activate <file> <path>. TUI: deactivate <path> / activate <path>. Engine-level, no schema annotation required. |
| Environment Variables | Supported | Ze-native env surface: ze.user, ze.pid.file, ze.pprof, ze.bgp.openwait, ze.bgp.announce.delay, ze.cli.format; ExaBGP-compat env keys retired 2026-04 |
| Outbound source-address | Supported | Optional source-address local-IP binding for outbound service connections: BMP collector, RPKI/RTR cache, flow-export (UDP), IRR whois, and the managed hub TLS client; LDP binds its transport-address as the session source (RFC 5036). Unset = OS-selected source (unchanged). Shared network.RealDialer applies the bind for TCP/TLS; flow-export binds the UDP socket. See Outbound Source Address. |
| CLI Default Output Format | Supported | Configurable default output format via environment { cli { format { default text; } } }. Supported values: text (default), table, json, yaml, ndjson. Session override via set cli format <value> in operational mode. Explicit pipe operators (\| json, \| table, etc.) always win over the configured default, and over the --format flag of ze cli -c. |
| Interfaces | Experimental | Linux interface management via netlink: ethernet, dummy, veth, bridge, loopback, VLAN (with 802.1p ingress/egress QoS maps translating PCP to internal priority and back, and named class-of-service profiles via the cos plugin with interface-level inheritance and per-unit override/opt-out; dynamic per-subscriber CoS via RADIUS Filter-Id "cos:\<name>" or vendor-specific attributes (Cisco-AVPair, Juniper ERX, Nokia, Huawei) applied on session-up with mid-session CoA changes and session-down revert; MikroTik Mikrotik-Rate-Limit VSA for shaper rate extraction ), 9 tunnel kinds (GRE, GRETAP, IP6GRE, IP6GRETAP, IPIP, SIT, IP6TNL, IPIP6, VXLAN with a 24-bit VNI over UDP 4789 per RFC 7348) , and WireGuard (declarative peers with $9$-encoded keys); DHCP (config-driven, routes, DNS, NTP discovery), NTP client (clock sync, RTC, time persistence, max-step safety cap), monitoring, migration, mirroring, per-interface offload and steering (GRO, GSO, SG, TSO, LRO, hw-tc-offload via kernel ethtool ioctl; RPS, RFS via sysfs; boolean three-state: true/false/absent), per-family reverse path filtering (rpf-check strict|loose|disable in ipv4/ipv6 containers, backed by rp_filter sysctl on Linux; IPv6 enforcement requires VPP), per-interface rate tracking (1s sampler with show interface rate, monitor interface rate, 12 ze_interface_* Prometheus gauges, web rate columns). Local privileged integration covers first-apply non-adoption, reload deletion scoped to previously managed links, and rollback of created kernel links after partial apply failure; target-runner evidence is still required before production deployment claims. Every kernel route the interface layer installs (DHCP, RA, PPPoE, PPP) carries rtm_protocol 253, the ze-iface producer id, and a delete matches on that protocol, so one producer's expiry cannot remove an operator static route. A default route learned on a unit takes metric 254 by default (route-priority, range 0..4294966271), on an interface unit and on a pppoe-client. The NTP client is compile-out-able with ze_ntp (the show system NTP block reads it through the nil-safe registry seam) and the class-of-service profiles with ze_cos; the dynamic per-subscriber CoS handler additionally requires ze_l2tp (a BNG feature: no sessions, no dynamic CoS). |
| IPv6 Router Advertisements | Experimental | Ze sends IPv6 Router Advertisements on a LAN interface unit (RFC 4861). This is the job radvd does on other systems. Hosts build addresses by stateless address autoconfiguration (SLAAC), learn a default router, and learn DNS resolvers (RFC 8106 RDNSS, up to 8 servers). The router-advertisement container sits in the per-unit ipv6 container. It carries the M and O flags, the per-prefix L and A flags, four timers, a prefix list, and the resolver block. The container is Linux only and netlink only. A backend vpp tree rejects it at config verify. Config verify applies the cross-leaf rules a YANG range cannot express. minimum-interval is at most 0.75 x maximum-interval, and preferred-lifetime is at most valid-lifetime. A prefix with host bits is rejected rather than masked, and so is the link-local prefix. Two zero values are legal input. router-lifetime 0 advertises prefixes and resolvers while Ze is not a default router. rdnss lifetime 0 tells hosts to stop using the resolvers. The iface-ra plugin owns the socket and the timers. Every advertisement leaves with Hop Limit 255. The sender joins for Router Solicitations and sends to. Each unsolicited interval is random between the two configured bounds, and a solicited answer is rate limited. A sender that stops sends up to three advertisements with a Router Lifetime of 0. Counters are ze_iface_ra_sent_total{interface} and ze_iface_ra_solicited_total{interface}. ze doctor reports doctor-iface-ra-forwarding when an advertising interface has IPv6 forwarding off. . ff02::2ff02::1 |
| Plugins | ∿ | The peer's config decides which program sees which peer: each peer attaches a program in an attach process block, receive states what that program is fed and send what it may originate toward that peer, and a peer that attaches nothing feeds nothing. A group's block reaches every member it produces, dynamic members included. show event delivery prints the edges. RIB, route server (with reactor-native RS fast path, dynamic peers, RS-client, community filtering), graceful restart, RPKI (ASPA policy enforcement), healthcheck, community filters, prefix-list filters, AS-path filters, AS-path length filters, community-match filters, per-address-family filters (remove an AFI/SAFI from an UPDATE on import or export -- e.g. a FlowSpec route reflector that never advertises ipv4/flow back to edge peers via export remove ipv4/flow -- or tear down the session on a received family; applies to forwarded and originated/injected routes), route attribute modifiers (set/increment/decrement for local-preference/med/aigp, community add/remove for standard/large/extended, MULTI_EXIT_DISC removal on an import chain as RFC 4271 Section 5.1.4 requires; a modifier can carry its own match container over standard, large and extended communities, and a route that meets none of the stated values passes through UNCHANGED rather than being rejected, because the chain already drops a rejected route and a conditional modifier exists so the routes it does not touch keep flowing), ingress community hygiene on the community filter (RFC 7454 Section 11 own-Global-Administrator scrub with a function keep-list, the RFC 8195 Section 3.2 relation-to-origin tag driven by the RFC 9234 role, and the RFC 7999 propagation guard), remove-private-AS policy action, AIGP (RFC 7311), BMP (RFC 7854), interface monitoring, cross-protocol redistribute (egress; routes injected by a redistribute source replay to a BGP peer that establishes after the injection, via a re-emit-on-peer-up request), FlowSpec-to-firewall bridge. BMP Loc-RIB and some redistribution claims remain explicitly scoped by readiness review. |
| BMP Delivery (RFC 7854) | Supported | Each collector session owns a bounded FIFO transmit queue, drained by its own goroutine off the producer's, so a slow collector never blocks the BGP path. The bound is 256 MiB of queued-but-unwritten bytes per session, sized to absorb a full Loc-RIB dump (about 1M IPv4 best paths at roughly 120 bytes of Route Monitoring each is about 120 MB). Reaching it resets the session rather than dropping a message, because RFC 7854 has no back-pressure signal. The byte bound is NOT the defense against a wedged collector: one that stops reading entirely is caught seconds earlier by the per-write deadline. It bites for the other shape, a collector that keeps reading but steadily slower than Ze produces, where every write succeeds and the backlog is what grows. A Loc-RIB dump closes every family it owes, including one with nothing to send. |
| BFD Liveness Detection | ∿ | RFC 5880 Bidirectional Forwarding Detection plugin: pinned single-hop (UDP 3784) and multi-hop (UDP 4784) sessions, profile-driven timer bundles, GTSM enforcement (IP_TTL=255 outbound / IP_RECVTTL ingress gate), multi-hop min-TTL floor, RFC 5880 §6.8.7 TX jitter (0-25%, clamped to [10%, 25%) when detect-multiplier=1), SO_BINDTODEVICE for single-hop interface and multi-VRF binding, BGP peer opt-in with RFC 9384 Cease subcode 10 teardown, show bfd sessions/session/profile commands, ze_bfd_* Prometheus metrics, RFC 5880 §6.7 Keyed SHA1/MD5 (meticulous variants included) authentication with file-backed sequence-number persistence, and RFC 5880 §6.4 Echo mode config/wire advertisement (transport half tracked as spec-bfd-6b-echo-transport). Compile-out-able with the ze_bfd build tag (default-on in ZE_FEATURES): a stripped build drops the engine, session, transport, auth, and command surface and rejects a bfd {} block, while the nil-able client seam (bfd/api, with the bfd/packet State/Diag types) stays always-on so BGP, OSPF, and static route monitors run without BFD by their existing warn-and-degrade path. |
| Kernel Tunable Management | Experimental | Sysctl plugin centralizes kernel parameter management with three-layer precedence (config > transient > default). Plugins declare required defaults (e.g., fib-kernel enables forwarding), users override via config or CLI. Original values restored on clean stop. Named profiles group co-dependent tunables (dsr, router, hardened, multihomed, proxy) applied per interface unit. User-defined profiles supported. CLI: show sysctl, ze sysctl list, ze sysctl describe, set sysctl, ze sysctl list-profiles, ze sysctl describe-profile. <!-- source: internal/core/sysctl/profiles.go -- ProfileDef, MustRegisterProfile, builtinProfiles… |
| Connection Tracking Management | Experimental | Declarative conntrack configuration under system { conntrack {} }. Helper module loading (ftp, sip, h323, pptp, tftp, sane, irc, amanda, netbios-ns, snmp, nfs, sqlnet) via modprobe on Linux (load-only, never unload). User-friendly config for table sizing (table-size, hash-size, expect-max), per-protocol timeouts (TCP, UDP, ICMP, ICMPv6, GRE, SCTP, DCCP), TCP behavior flags (be-liberal, loose, max-retrans, ignore-invalid-rst), and global flags (accounting, timestamp, checksum, log-invalid). All sysctl values routed through the sysctl plugin for three-layer precedence. Dual-setting prevention rejects keys in sysctl {} that conntrack manages. On gokrazy (modules built-in), module loading is skipped gracefully. CLI: show system conntrack. Telemetry: configured-max gauge alongside existing per-CPU counters. |
| Installation | Experimental | ze install local copies the binary to a standard FHS prefix (/usr/local, /usr, /opt/ze) and scaffolds the config directory if no database.zefs exists. ze install systemd manages the systemd service: requires an existing database.zefs, creates the ze user/group, writes, grants the needed network capabilities, enables the unit, and can start it with --start. ze uninstall systemd stops, disables, and removes only the service unit. ze install remote provides PXE-based bare-metal provisioning: generates ze config from CLI flags, forks ze - to start DHCP+PXE (dhcpserver plugin with options 43/60/66/67/77/93, BIOS/UEFI bootfile selection, iPXE chainloading via boot-script-url for automatic kernel cmdline), TFTP (RFC 1350 read-only tftpserver plugin with RFC 2347 option negotiation for UEFI PXE), and HTTP image server (imageserver plugin with Range support, dynamic boot.ipxe generation with ze.server/ze.image/ze.port). --kernel and --initrd flags auto-stage installer artifacts; stock iPXE binaries bundled in are copied to the TFTP directory if not present. ze appliance iso builds bootable amd64 or arm64 UEFI installer media around an existing appliance image, verifies the image checksum before ISO creation, and boots an initrd path that decompresses and writes the gzip-compressed embedded image while skipping PXE-style ZeFS download. ze uninstall local removes the binary (optionally config with --purge). Bootstrap mode: when ze starts with zefs but no config and no template, it discovers all interfaces, enables DHCP client on every ethernet NIC, and starts SSH for operator access. Non-ethernet interfaces are skipped. SSH credentials come from zefs (pre-provisioned by the installer). The installer initrd is a single statically-linked Go binary (cmd/ze-installer, packed into a pure-Go cpio with zero external binaries) running as PID 1 that either downloads the gokrazy disk image via HTTP or mounts appliance ISO media read-only, writes the selected image to a safe target disk, injects the zefs database only in HTTP mode, reboots in HTTP mode, and powers off in ISO mode so removable media can be removed before the next boot. Appliance and gokrazy images compile the in-image ze binary with the ze_core build tag (the base personality). Positive build tags (ze_distro, ze_appliance, ze_setup) add feature sets on top. Finer per-feature compile-out tags (ze_<feature>, e.g. ze_lg for the looking glass, ze_ssh for the ssh server) include or exclude individual optional services: make ze-build/ze-appliance enable the default-on set (ZE_FEATURES in the Makefile), while ze-stripped omits them for a smaller, hardened binary; an omitted service is not linked and its config block is rejected as unknown. Alternate binaries (ze-test, ze-chaos, ze-perf, ze-analyze) each have their own tag and exclude core. Build the initrd with ze appliance initrd. The netboot pair behind ze install remote (tftpserver + imageserver) is compile-out-able with the shared ze_pxe build tag, and the DHCP server with ze_dhcpserver: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds. . /etc/systemd/system/ze.servicetools/ipxe-binaries/ |
| Modular Deployment | ∿ | Config-driven plugin loading: BGP, interfaces, and FIB load only when their config section is present. Add or remove subsystems at runtime via config reload (SIGHUP). Required config-root autoload failures fail closed, reload diffs restart same-name plugins when their definition changes, and changed external plugin replacements are pre-started before the old handler is removed. |
| Static Routes | Supported | Config-driven static route plugin with named routing tables (policy-based routing), interface-only next-hops (PPPoE/GRE tunnels), mixed ECMP (gateway + interface-only in same group), ECMP (multiple active next-hops), per-next-hop weighted load balancing, BFD-tracked failover (next-hop removed from ECMP group on session DOWN, re-added on UP), blackhole/reject, IPv4/IPv6, config reload reconciliation, and redistribute integration (redistribute { import static }). Named tables resolved via routing-table registry; non-default table routes are PBR-only (not redistributed into BGP). Programs kernel via netlink multipath or VPP via GoVPP. |
| Connected Routes | Supported | Redistribute directly connected interface prefixes into BGP via redistribute { import connected }. Subscribes to interface address events; emits RouteChangeBatch on address add/remove. Reference-counted: multiple addresses on the same prefix emit one announcement, withdrawn only when the last address is removed. IPv4 and IPv6. No kernel programming (kernel already has connected routes). |
| Kernel Routes | Experimental | Redistribute externally-installed kernel routes into BGP via redistribute { import kernel }. Consumes parsed route events from a shared netlink route watcher, filtering Ze-owned routes (rtproto 250-252), RTPROT_KERNEL (2), and RTPROT_REDIRECT (1). Emits RouteChangeBatch for DHCP (16), PPP/manual (BOOT=3), admin static (STATIC=4) routes. Tracks announced prefixes; withdraws all on shutdown. IPv4 and IPv6. Shares a single netlink subscription with fib-kernel (route re-assertion). . internal/core/routewatch/ |
| Policy Routing | Experimental | Policy-based routing via nftables packet marking and kernel ip rules. Steers traffic to alternate routing tables or next-hops based on L3/L4 match criteria (address, port, protocol, TCP flags, set references). Actions: accept (bypass), drop, table N (fwmark + ip rule), next-hop (auto-managed table from 2000-2999), tcp-mss clamping. Interface wildcard binding (e.g., l2tp*). Config reload reconciles nftables tables, ip rules, and auto-managed routes. Compile-out-able with the ze_policyroute build tag: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds, with its config block rejected as unknown. |
| RPF Lookup | Supported | Reverse Path Forwarding query: longest-prefix-match against Loc-RIB for any CIDR family (IPv4/IPv6 unicast/multicast). Exposes show bgp rib rpf <family> <source-addr> command returning matched prefix, next-hop, admin distance, and metric as JSON. Generic LPM on the sharded Loc-RIB (queries all shards, picks most specific). |
| Route Installation | Experimental | FIB pipeline: protocol RIB best-path tracking, system RIB selection by admin distance, recursive next-hop resolution (max depth 8, IGP metric accumulation, cascade on NH change), ECMP grouping (equal-cost paths collected into nexthop groups, max 128 members), rich route programming (route type blackhole/unreachable/prohibit, metric, VRF table ID, MPLS labels, SRv6 SID), kernel backend via netlink multipath with route type/metric/table/MPLS-lwtunnel/SRv6-seg6 support, VPP backend via GoVPP with multi-path/route-type/metric/table support, per-producer netlink protocol ownership, crash recovery via stale-mark-sweep, external change monitoring. The forwarding action (unicast, blackhole, unreachable, prohibit) rides one route from the protocol RIB through sysrib into the FIB backend. It is deliberately NOT part of arbitration: selection stays on priority and then protocol name, so a discard route never beats a forwarding one on the strength of being a discard. It IS part of the change comparison, because a prefix that turns into a discard with every other field unchanged must still reach the kernel. show rib reports it as route-type, omitted for an ordinary route, and a Linux discard route names no next-hop, device, multipath or encap. CLI: show nexthop-table, show ecmp-groups. Local privileged integration covers FIB restart sweep and flush-on-stop preserving static and policyroute-owned routes; target-runner evidence is still required before production deployment claims. |
| IS-IS | Experimental | Native IS-IS link-state IGP (ISO/IEC 10589, RFC 1195/5305/5301/5303/5308) running directly over Layer 2: L1+L2 levels, broadcast + point-to-point circuits with DIS election and pseudo-nodes, adjacency FSM, link-state database with flooding (CSNP/PSNP sync), SPF, and FIB install via the shared Loc-RIB. Authentication (RFC 5304 HMAC-MD5, RFC 5310 generic crypto / HMAC-SHA, and cleartext) is configured as named key-chains with $9$-encoded secrets: per-interface chains authenticate Hellos (IIH), per-level chains (area key L1 / domain key L2) authenticate LSPs and CSNP/PSNP; the Authentication TLV is emitted first, LSP digests zero the Authentication Value + Checksum + Remaining Lifetime before signing (Fletcher checksum computed after), purges are authenticated, key rotation is hitless via send/accept lifetimes, and every digest compare is constant-time. Auth-failure rejections increment ze_isis_auth_failures_total{level,interface}. Redistribution meshes IS-IS with BGP in both directions through the protocol-agnostic redistribute framework: a single source isis exports SPF routes (redistribute { destination bgp { import isis } }), and an isis consumer imports connected/static/BGP prefixes into IS-IS LSPs as Extended IP Reachability (TLV 135, redistribute { destination isis { import connected/static/bgp } }); enabled and passive interface prefixes are also advertised. TLV 135 carries no external bit (RFC 5305 sec 4); the up/down bit is set only on a down-level leak (RFC 2966). Redistribution counters are ze_isis_redist_injected_total{source,afi}, ze_isis_redist_withdrawn_total{source,afi}, ze_isis_redist_inject_failures_total{source}, and ze_isis_lsp_reoriginations_total{level}. Dual-stack IPv6 (RFC 5308) runs over the same instance under single-topology: per-interface address-family ipv6-unicast advertises NLPID 0x8E (TLV 129), carries the link-local address in the Hello (TLV 232) and non-link-local addresses in the LSP, originates IPv6 prefixes as IPv6 Reachability (TLV 236, link-local prefixes excluded per RFC 5308 sec 2), runs IPv6 route extraction over the shared SPF tree (a TLV 236 metric above MAX_V6_PATH_METRIC 0xFE000000 is ignored), and installs IPv6 routes via the same Loc-RIB path (show isis route ipv6); IPv6 redistribution works both ways under the single isis source with an afi=ipv6 counter label. Non-congruent IPv4/IPv6 topologies are not supported, because RFC 5120 Multi-Topology is not implemented. LSDB operations: every show isis database row carries an own boolean, so an operator sees at a glance which LSPs this node originated. A received claim on one of this node's own LSP IDs, a purge included, is never overwritten: the node raises its origination sequence floor above the claimed number, re-originates, and floods, which is ISO/IEC 10589 clause 7.3.16.4 c) deferring to clause 7.3.16.1. The dynamic hostname is refused at config time outside 7-bit ASCII, and bounded per label and in total, per RFC 5301 Section 3 and RFC 2181 Section 11, so an unencodable value fails the commit rather than the wire. Live adjacency, flooding, SPF, dual-stack, and FRR interop run as QEMU integration / interop tests. The IS-IS plugin is compile-out-able with the ze_isis build tag: make ze-build and make ze-appliance-build include it (default-on in ZE_FEATURES), while ze-stripped and bare ze_core builds drop internal/plugins/isis (engine, codec, transport, cli, schema) from both composition roots and reject the isis {} config block as unknown. |
| OSPF | Experimental | Native OSPF engine with one ospf config root for OSPFv2 IPv4 and OSPFv3 IPv6 (address-family ipv6): raw protocol 89 transports, interface and neighbor state machines, LSDB flooding/aging, intra-area SPF, ABR summary origination, inter-area route calculation with area ranges, ASBR external origination and redistribution, OSPFv3 Link-LSAs and Intra-Area-Prefix-LSAs, stub / totally-stubby / NSSA areas (RFC 3101: Type 7/NSSA origination, Highest-Router-ID translator election with a stability-interval grace so a transient flap opens no Type 5 gap, Type 7 to Type 5 translation, §2.5 preference; Section 2.4's border-router default is originated into every directly attached NSSA with no operator gate, as a P-clear Type 7 into a regular NSSA and a Type 3 at default-cost into a no-summary one, which leaves default-originate meaning only an internal router's P-set Type 7 default, itself conditional on a usable non-zero forwarding address; the OSPFv3 defaults are not complete), virtual links through a transit area (RFC 2328 §15 / RFC 5340 §4.2: config-validated transit-area/ABR/self rules, transit-area-SPF-computed cost, backbone Router-LSA virtual record + transit-area V-bit, §16.3 transit pass, routed transport), the RFC 5250 opaque-LSA carrier with RFC 3630/5392 Traffic Engineering and the RFC 7770 Router Information LSA (OSPFv2 Opaque type 4, OSPFv3 function code 12, informational capability bits, a consumer-neutral TLV hook), the RFC 7684 Extended Prefix/Link Opaque LSAs (Opaque type 7/8, prefix/link attribute containers with a generic sub-TLV registration hook for Segment Routing), Graceful Restart (RFC 3623 IPv4 + RFC 5187 IPv6, one shared restarter + helper control plane: the IPv4 Grace-LSA rides the opaque carrier as Opaque type 3, the IPv6 Grace-LSA is a native link-scope LS type 0x000B, non-stop forwarding via the RTPROT_ZE FIB retention, an NVS restart fact, and a family-neutral graceful-restart config), Segment Routing over the MPLS data plane (RFC 8665 IPv4 + RFC 8666 IPv6, one shared control plane: SRGB/SRLB label ranges, the multi-range index-to-label arithmetic, the NP/E/M push/swap/PHP truth table, node Prefix-SIDs and SRLB-allocated Adjacency-SIDs, install through the shared mpls-fib bus as the third producer alongside LDP and RSVP-TE; the IPv4 family rides RFC 7770/7684 opaque LSAs and the IPv6 family the RFC 8362 Extended LSAs), LFA / TI-LFA fast reroute (RFC 5286: per-neighbour SPFs and the strict loop-free / node-protecting / downstream inequalities select a pre-computed backup next-hop per primary, with a TI-LFA post-convergence Segment-Routing repair list where no directly-connected LFA exists, the §6.3 multi-area suppression rules, and install as a link-down/backup FIB next-hop through a generic carry-through field on locrib.Path; IPv4 carries SR repair labels, OSPFv3 gets base-LFA next-hop selection through the address-family seam), per-interface OSPFv2 authentication with key chains and replay protection, ECMP, and FIB install through the shared Loc-RIB -> sysrib -> fibkernel path. show ospf exposes process, neighbor, interface, database (including opaque, te-database, and router-information views), route, SPF, and border-router views. The OSPF plugin is compile-out-able with the ze_ospf build tag: make ze-build and make ze-appliance-build include it (default-on in ZE_FEATURES), while ze-stripped and bare ze_core builds drop internal/plugins/ospf (engine, codec, transport, v3, cli, schema) from both composition roots and reject the ospf {} config block as unknown. |
| MPLS / LDP / RSVP-TE | Experimental | Kernel MPLS forwarding: BGP labeled-unicast routes programmed into the Linux MPLS FIB (label push via netlink lwtunnel), 20-bit label / 16-deep stack validation, net.mpls.platform_labels + per-interface mpls { enable } (net.mpls.conf.<iface>.input) sysctls, ze doctor kernel-module check, ze_fibkernel_mpls_* metrics, show mpls forwarding (reads the kernel AF_MPLS table). LDP (RFC 5036): UDP multicast discovery, TCP session FSM, label information base. RSVP-TE (RFC 3209/2205): control plane over raw IP (protocol 46) -- PATH/RESV signaling for ingress/transit/egress, ERO-based explicit routing, per-interface bandwidth admission control with PathErr on oversubscription, soft-state refresh, PathTear teardown, and make-before-break reroute (SE style). Fast Reroute (RFC 4090): facility backup -- a tunnel fast-reroute request adds FAST_REROUTE/SESSION_ATTRIBUTE protection flags to PATH; a transit Point of Local Repair arms a configured bypass, and on a link failure redirects the LSP onto it with a 2-label stack (the bypass label over the protected label), sends a PathErr Notify (code 25/3) toward the head-end without tearing the LSP down, and the head-end re-optimizes make-before-break; node protection merges at the next-next hop using RRO label recording. Dataplane: RSVP-TE/LDP emit push/swap/pop forwarding entries on the mpls-fib event bus and fib-kernel programs them (IP route + label for push, AF_MPLS routes for swap/pop, including the 2-label facility-backup stack), keeping fib-kernel the single kernel-FIB owner. ze-to-ze FRR is covered by the in-memory fabric interop; cross-vendor interop needs a proprietary RSVP-TE peer (no open-source one exists). LDP and RSVP-TE are independently compile-out-able with the ze_ldp and ze_rsvpte build tags (default-on in ZE_FEATURES; dropped from ze-stripped / bare ze_core, with their ldp {} / rsvp-te {} config blocks rejected as unknown); the MPLS operational surface (show mpls forwarding and the show mpls command schema, internal/component/mpls + internal/plugins/mpls-cmd) is compile-out-able with ze_mpls, while kernel MPLS forwarding itself (fib/kernel) stays always-on. |
| VRRP | Experimental | First-hop redundancy on interface units: RFC 9568 VRRPv3 for IPv4 and IPv6 (the default) with RFC 3768 VRRPv2 available per group via version 2 (IPv4 only). Groups are named by the operator and carry a mandatory vrid, configured under the unit's family container so the group sits next to the addresses it backs: interface { ethernet eth0 { unit 0 { ipv4 { vrrp { group uplink { vrid 10; virtual-address [ 192.0.2.1 ]; } } } } } }. Each group gets its own macvlan device carrying the RFC virtual MAC (for IPv4, for IPv6), so failover moves the L2 address and not just the L3 one, and hosts need no ARP re-resolution. Adverts run over IP protocol 112 to 224.0.0.18 / ff02::12 with GTSM TTL/hop-limit 255 enforced on transmit and receive; a Master transition emits gratuitous ARP (IPv4) and unsolicited Neighbor Advertisements (IPv6). The address owner is detected from the unit's configured addresses and runs at priority 255 per RFC 9568 Section 5.2.4; accept-mode, preempt, preempt-delay-seconds, and priority are per group. accept-mode is a known partial: it is parsed, validated (rejected under version 2), and reported by show vrrp, but is not enforced on the dataplane this pass, so the virtual address answers traffic while the router is Active regardless of the leaf (RFC 9568 Section 6.4.3 filtering is not installed). Cross-leaf rules the YANG grammar cannot express are enforced by an in-process verifier shared with ze doctor (doctor-vrrp-config-invalid, doctor-vrrp-backend-unusable, doctor-vrrp-raw-socket), so a commit and a doctor run can never disagree. Operational surface: show vrrp, show vrrp interface name <iface>, show vrrp statistics, clear vrrp statistics, plus ze_vrrp_state{device,group,vrid,family} and ze_vrrp_transitions_total for Prometheus. Netlink backend only: VRRP needs macvlan devices and raw sockets, so a VPP-backed tree is rejected rather than silently degraded. RFC 3768 authentication is not implemented by design (RFC 9568 Section 9 removed it as providing no real security). Virtual-MAC ownership of the virtual IP is enforced on the dataplane: the macvlan is created in private mode and the virtual IP carries the parent's subnet prefix, with arp_ignore/arp_filter/rp_filter sysctls (applied and later restored by the plugin) making the macvlan the sole ARP responder, so a host resolving the virtual IP gets the virtual MAC (mirrors keepalived's use_vmac). Experimental: the wire format is asserted against golden frames in QEMU and interoperability is proven against keepalived 2.3.1 (election, node-death failover, graceful stop, virtual-MAC ownership), but it has not been hardened in production. The VRRP plugin is compile-out-able with the ze_vrrp build tag: make ze-build and make ze-appliance-build include it (default-on in ZE_FEATURES), while ze-stripped and bare ze_core builds drop internal/plugins/vrrp (engine, FSM, packet codec, transport, schema) and reject the vrrp {} block under an interface unit as an unknown field; VRRP registers its CLI through the plugin registry, so it needs no dispatch-root companion. . 00:00:5e:00:01:{vrid}00:00:5e:00:02:{vrid} |
| Interactive Launcher | Supported | Running ze with no arguments in a terminal shows a BubbleTea menu of all commands grouped by section, with type-ahead filtering, scrolling, and drill-down into YANG verb sub-commands. Non-TTY invocations fall back to static help text. |
| CLI Commands | Supported | Protocol tools, config management, schema discovery, daemon control, AS topology graph, policy dry-run testing (show policy test). Verb-first is now the only spelling: every command starts with show, clear, monitor, request, set or delete, and the words after it are the YANG path. The bare forms are gone from the command tree, not aliased, so the dispatcher answers unknown command for daemon reload, daemon status, daemon quit, bgp summary, system memory, interface rate and the rest; the verb-first spellings are request reload, show status, request halt, show bgp summary, show system memory, show interface rate. Three exec verbs stay bare on purpose (stop, restart, reboot): the SSH exec middleware intercepts them before the dispatcher, which registers no key for them. show interface reshaped its keywords too: name <n> detail and name <n> counters replace detail <n> and counters <n>, and brief, scan, type, errors and rate each answer a distinct question. |
| CLI Session Transcript | Supported | Local transcript recording of ze cli and ze config edit sessions to. Preserves command input and output for post-disconnect recovery. Enabled via environment { cli { transcript enabled } } config or ze.cli.transcript env var. Best-effort writes never block CLI operation. . $XDG_DATA_HOME/ze/transcripts/ |
| API Commands | Supported | Peer management, route updates, RIB operations, event subscription |
| Configuration Reload | ∿ | Live reload via SIGHUP with automatic reconciliation. Reload stages edited config as a candidate version and promotes it to active only after runtime reload succeeds. Plugin-server transactions, config-provider roots, subsystem reload, and changed external plugin replacement roll back on failure; remaining reload safety depends on component-specific journals and privileged dataplane evidence. |
| Fleet Management | Experimental | Centralized config distribution over TLS |
| Performance Benchmarking | Supported | Cross-implementation latency benchmarking with ze-perf |
| Web Interface | Supported | HTTPS config editor with YANG-driven UI, CLI bar, and L2TP session management (CQM graph, event timeline, disconnect). Every page is a .templ source compiled to Go (58 in the web package, 9 in the Looking Glass), so a renamed view-model field is a compile error rather than a blank panel; regenerate with make generate. The browser library is htmx 4.0.0-beta6, shared by the web UI, the Looking Glass, and the chaos dashboard. The service is default-on but compile-out-able with the ze_web build tag: make ze-build and make ze-appliance-build include it, while minimal ze_core and ze-stripped-build builds drop internal/component/web. Self-signed HTTPS certificates are generated by the shared internal/core/selfcert helper and persisted through caller-owned storage. |
| Looking Glass | Supported | Public BGP looking glass with birdwatcher API, AS path graphs, and BMP-monitored route display. Runs on its own configurable port, separate from the web UI. tls defaults to true. An optional token gates every route and is compared in constant time; unset leaves the looking glass open, which is the default. Pages render through templ with typed view models, not html/template. |
| AI-First Design | Supported | Self-describing CLI-as-API with MCP transport for AI assistants |
| Self-Documenting System | Supported | Runtime introspection of plugins, env vars, RPCs, schemas, commands |
| Host Inventory | Supported | Structured hardware inventory for ISP fleet monitoring: CPU (vendor, topology, hybrid P/E layout, scaling driver, frequencies, throttle counts), physical NICs (driver, PCI IDs, link speed, queue counts, firmware, rings), DMI board identity, memory with ECC counters, hwmon thermal sensors + per-CPU throttle, block devices with NVMe firmware, kernel release/cmdline/microcode/arch flags. Read-only sysfs/procfs, no daemon required. Single show host cpu/nic/... command served by the daemon when reachable, falling back to the same in-process detection when no daemon is running; JSON by default for pipeline consumption. |
| Self-Update | Supported | Platform-aware update backend. Normal Linux uses Ze self-update or passive version checking with SHA-256 verified download, atomic binary replacement via rename, .prev hard-link rollback, deterministic spread scheduling (FNV-1a per device+version), maintenance windows, server-side pause, and persisted update history. Gokrazy appliances report backend: gokrazy-ab through the gokrazy backend and keep the manual update system firmware {check,download,apply,restart,rollback} command family wired; unsupported operations report that system image updates are managed by gokrazy. Minimal (no-tag) builds return an explicit self-update unavailable in minimal build response. ze update serve standalone server remains available on Ze-managed platforms to distribute artifacts. Config: system { update-check { auto-apply true; spread 1800; maintenance-window { start 02:00; end 06:00 }; restart { time 03:00 } } }. |
| Operational Report Bus | Supported | Cross-subsystem ze show warnings and ze show errors commands with source <name> filtering: single place to surface prefix-threshold crossings, stale route data, BGP NOTIFICATIONs sent/received, unexpected session drops, session-stuck/flap/EOR-timeout, route-count-anomaly (>50% drop), FIB sync failures/orphans/programming-lag, firewall stale-table/drift, plugin crashes, interface error counters. State-based warnings + event-based error ring, login banner reads the same source. |
| Local Audit Trail | Supported | Structured local audit log for config commit/discard, daemon reload, and failed authentication across SSH, web, REST, gRPC, MCP, CLI, and system surfaces. show audit filters by action, actor, surface, time range, and count. Disk-backed JSON-lines storage is used when Ze starts from a config file; stdin configs use memory-only storage. |
| Health Registry | Supported | Aggregated component health via show health and /health HTTP endpoint (503 when any component is down). Registered components: l2tp, report-bus, ipsec, pki, bgp (session-stuck/flap/EOR), fib (sync-failure/orphan/lag), firewall (stale-table/drift audit), iface (error counters), plugins (crash/disabled), vpp (API socket probe). Each check is expected to return within 1 second by contract; the registry recovers panics but does not enforce a timeout. |
| Storage SMART Management | Supported | YANG-modeled SMART disk health: auto-enable on detected ATA/NVMe devices, periodic health polling with three-tier temperature alerting (informational warning, rate-of-change warning, critical error) via report bus, scheduled self-tests (short daily, extended weekly with day-of-week constraint), in-progress detection (skips duplicate tests), live status via show storage smart (per-device health, temperature, power-on hours, error count, NVMe percent-used/available-spare, self-test schedule). Pure ioctl (no smartctl binary, gokrazy-safe). Config reload updates intervals live. ze doctor verifies SMART accessibility when enabled. First NOS with YANG-modeled SMART management. |
Tech-Support Bundle (ze support) |
Supported | Offline archive generator with 20 pure-Go modules (no shell-outs, gokrazy-safe): version, doctor, host, platform, config (sanitized), crashes, disk, interfaces (netlink), routes (netlink), neighbors (netlink), env, sysctl, runtime, dmesg, sockets, kernel modules, conntrack, file descriptors, DNS, firewall (nftables via netlink). Module selection (--module/--exclude), time scoping (--since), privacy-by-default (--sensitive to include secrets), reason metadata (--reason), JSON manifest (--json). Archive: ze-support-<hostname>-<timestamp>.tar.gz with one JSON file per module. No NOS vendor produces structured JSON-per-module output. SMART disk health via direct ATA/NVMe ioctls (no smartctl binary). |
System Readiness (ze doctor) |
Supported | Offline pre-start checks: runtime platform detection (gokrazy, systemd, container, plain-linux, darwin) with capability probing (read-only root, /perm, systemd, gokrazy socket, reboot, persistent storage), config syntax, YANG validation, TLS certs (missing/expired/invalid), VPP socket, kernel modules, interface existence/state, SSH host key, listener conflicts, plugin binaries, storage integrity, dangling config references, disk space (<5% free), DNS resolver reachability, clock skew (>5 min vs NTP), VPP version (Linux), VPP hugepage reservation (Linux). Platform-aware checks: gokrazy /perm writability, container read-only root, missing Ze-managed clock synchronization, platform-mismatched persistence/DNS paths, and Linux machine-id presence. Config-delivery audit: doctor-config-root-unclaimed names a configured subtree that commits and reaches no plugin and no hub handler, with doctor-config-claims-unavailable when the claim set cannot be read. Other codes added since: doctor-tls-reference (a certificate name the PKI store does not hold), doctor-bgp-capture-directory (a capture directory the daemon cannot write), doctor-iface-ra-forwarding (a unit advertising while its device has IPv6 forwarding off), and doctor-vpp-lcp-plugin (vpp.lcp on while the running VPP loads no linux_cp_plugin.so; a failed probe degrades to a warning rather than an error). --json output with stable diagnostic codes and ze explain <code> for remediation. |
| Granular Debug | Supported | Verb-first debug (set/delete/show/clear, matching VyOS syslog-level config) with per-module flag/direction/scope filtering and named profiles. CLI: ze set debug module <name> (enable), ze delete debug module <name> (disable), ze set debug module <name> flag <flag>, ze set debug module <name> scope direction <dir>, ze set debug module <name> level <level>. Hierarchical prefixes work (ze set debug module bgp covers all bgp.* subsystems). Named profiles: ze set/delete debug profile name <name>, ze show debug profile [name <name>], ze set debug active name <name> (apply). Stored in debug.zefs (separate from config). Each plugin declares its valid flags via the debug YANG registry. Not auto-applied on reboot (safety). show debug (YANG-dispatched) queries live daemon state. |
| Runtime Diagnostics | Supported | Production debugging via CLI and MCP: show l2tp observer (per-session event ring), show l2tp cqm (per-login echo RTT/loss buckets), show l2tp echo (current echo state), show l2tp reliable (reliable transport Ns/Nr/cwnd), show traffic control (TC qdisc/class state), show metrics pool (BGP attribute pool occupancy and dedup rates), enhanced subsystem-list (real plugin state). All auto-exposed as MCP tools for AI-assisted troubleshooting. |
| Core Diagnostics | Supported | 11 built-in diagnostic commands replacing ss, dmesg, lsof, dig, nc, traceroute, mtr, ping, tcpdump, and pprof on gokrazy appliances: show system sockets (TCP/UDP state), show system kernel-log (dmesg), show system goroutines (dump with singleflight dedup), show tcp-check (port probe), show traceroute (ICMP path trace with per-hop RTT, IPv4/IPv6), monitor traceroute (live mtr-style continuous trace with | log | resolve and | log | origin enrichment), monitor ping (continuous ICMP ping with live stats), show capture interface (AF_PACKET live capture with BPF filters, pcap or text output), show system file-descriptors (FD counts and limits), show dns lookup/cache (DNS resolution, cache listing, selective delete, flush, stats reset), show system profile (cpu/heap pprof), show system memory (/proc/self/status OS view). monitor system netlink streams kernel route/link/address change events as JSON (replaces ip monitor). BFD raw capture ring. Root privilege enforcement at startup. See Production Diagnostics Guide. |
| Protocol Event Capture and Replay | Supported | Per-peer, opt-in JSONL capture of a BGP session's inbound protocol events, and a replay harness that feeds a capture back through the same read path. Capture tees the COMPLETE wire message at both read paths (standard and coalesced) before RFC 7606 enforcement and before coalescing, so a malformed UPDATE is recorded exactly as the peer sent it and a coalesced batch is recorded as the separate messages it arrived as. Config operations applied while a capture runs are recorded with their transaction id, with secret-bearing values redacted. Off by default: the tee costs one nil comparison and no allocation per received message. Each file is capped at capture { maximum-size } megabytes exactly (1-1024, default 100); at the cap it rotates once to <file>.1 or stops, per capture { on-limit }, so a peer uses at most twice the cap on disk. Writer backpressure sheds events rather than blocking the read loop, counts them in ze_bgp_capture_dropped_events_total, and writes the gap into the stream. ze doctor reports a capture directory the daemon cannot write. Replay: ze-test replay <file> drives Session.ReadAndProcess over a stub connection and an injected clock and reports the FSM transitions, the prefixes announced and withdrawn, and any NOTIFICATION the session sent. |
| Crash Capture | Supported | Automatic stderr redirect captures panic stack traces from any goroutine. Forwarded to syslog (via ze.log.destination) in real time and persisted to crash files on disk. Crash reports include ring buffer context (last 64 log entries before the panic), version, build date, uptime. Crash dir autodetected (on gokrazy, fallback chain for other platforms). CLI: show crashes, show crashes latest. Env vars: ze.crash.dir, ze.crash.keep. . /perm/ze/crash/ |
| Interoperability Testing | Supported | 100+ Docker-based interop scenarios against FRR, BIRD, and GoBGP; OpenBGPD, FreeRtr, and Rust implementations are exercised as performance-benchmark DUTs, not interop scenarios |
| RFC Conformance Gate | Supported | 171 documents are enrolled (167 RFCs, 3 drafts, and sFlow v5), declaring 2,965 MUST-level requirements that make ze-rfc-check gates. Each one is either proven by a positive AND a negative tagged test, or annotated with a written reason. The requirement text is authored in and the test links are DERIVED from RFC requirement: tags in the tests themselves, so a claim cannot be typed in. indexes one file per RFC under, regenerated by make ze-rfc-index-update and gated for freshness. Every evidence link carries a kind/tier cell, because a unit test proves the algorithm, a .ci proves the daemon exposes the behavior, and an interop scenario proves a foreign peer accepts it; a tag in a carrier nothing executes is REFUSED rather than listed with a caveat. Eight machine checks stop coverage moving backwards: enrolment, coverage, retired requirements, new summaries, evidence kind and tier, extraction sign-off, and public disclosure are ratchets, and check_drain_floor reads a schedule from. Extraction sign-off (rfc/extraction/<stem>.json) records a reviewer's classification of every normative site in an RFC's own text, which bounds what the checklist could have missed; 7 of the 171 carry one. . rfc/short/*.mdai/RFC-REQUIREMENTS.mdrfc/requirements/rfc/drain-budget.txt |
| Testing Health | Supported | Generated report on whether a regression would actually be caught, as opposed to how many tests exist: RFC MUST requirements proven by a positive+negative test pair (as distinct from those annotated away), tests with no reachable failure call, test files no go test target can build, mutation kill rate, negative-test ratio per subsystem, and technique adoption bucketed by package age. Ratios always carry numerator and denominator, an unmeasured metric renders unknown rather than green, and the assert-nothing and tag-orphan counts are ratcheted by make ze-precommit-verify so they can only fall. KPI history is committed to, so evolution is visible across releases. . test/health/history.ndjson |
| gNMI | Supported | Industry-standard gRPC Network Management Interface for YANG-modeled config. Capabilities, Get, Set (via segment-based paths preserving IP list keys), Subscribe ONCE and STREAM modes. Bearer token auth with constant-time comparison, optional TLS. YANG config schema under environment { gnmi {} }, show gnmi CLI command, Prometheus counters (ze_gnmi_requests_total, ze_gnmi_subscribe_active, ze_gnmi_errors_total). External config commits (web, CLI, managed) notify STREAM subscribers. Env-var gated (ze.gnmi.enabled), default port 9339. The service is default-on but compile-out-able with the ze_gnmi build tag: make ze-build and make ze-appliance-build include it, while ze-stripped and bare ze_core builds drop internal/component/gnmi, its schema, and its show gnmi RPC. |
| REST/gRPC API | ∿ | Programmatic API with OpenAPI 3.1 spec, config sessions. Both transports accept multiple named listen endpoints via environment.api-server.rest.server <name> / .grpc.server <name>. REST is plaintext and therefore loopback-only; expose it remotely only behind a TLS terminator. Non-loopback authenticated gRPC listeners require TLS. Bearer token auth, per-user auth, CORS support. Both transports share one engine for identical command output. SSE and gRPC streaming are wired to registered streaming commands such as monitor event, using the same authorization and accounting path as SSH monitor commands. Completion remains future work. Each transport is independently compile-out-able: ze_rest gates internal/component/api/rest and ze_grpc gates internal/component/api/grpc, so a build can ship gRPC-without-REST or vice-versa. make ze-build/ze-appliance include both; ze-stripped and bare ze_core drop both. With a transport compiled out its server code and its config container (rest{}/grpc{}) are absent, so that block is rejected as unknown; the shared api-server { token } base and the parent internal/component/api engine stay always-on (gNMI uses the parent). |
| Named Service Listeners | Supported | Every service that accepts inbound connections (web, ssh, mcp, looking-glass, telemetry, REST, gRPC, plugin hub) models its listen endpoints as a named YANG list. Each entry binds its own listener on the same subsystem; bind is all-or-nothing with rollback on failure. CollectListeners detects overlapping ip:port pairs at config parse time across every service. |
| Management Listener Exposure Guard | Supported | One boot-time, fail-closed check refuses to start a management service on a non-loopback address without authentication. It covers the insecure web listener, MCP, gNMI, and the API server (REST and gRPC). It runs once, after every address and credential is resolved and before anything binds, so a refusal leaves nothing bound and the process exits non-zero. Fail-closed on classification too: a wildcard (0.0.0.0, ::), an empty host, and any DNS name including localhost all count as non-loopback, so remote reachability cannot be smuggled past it through a name. An unauthenticated surface that declares no resolved address is refused rather than passed by iterating zero times, which is the empty-set trap that once let an insecure web server reach. The refusal names the service, the address, and the fix, and never prints a token. For MCP the guard mirrors the server's own precedence: a token written beside an explicit auth-mode none does not authenticate, because the server builds its accept-all authenticator in that case. . 0.0.0.0:3443 |
| Live Credential Revocation | Supported | Every credential surface reads the running config's user list, so a user the config stops declaring stops authenticating at the next reload: the web password, the web session cookie, the SSH password, the SSH public key, and REST/gRPC Bearer. The revoker is identified rather than assumed. An authentication result names the backend that produced it, and only a result the local backend granted is revoked by the local list; an empty source is not local, because reading silence as local would attach the local list's revocation to a session some other backend granted. A session a RADIUS or TACACS+ backend granted is therefore not revoked by an edit to the local users. Revocation governs new authentication; an open session survives by design. |
| Custom Value Validators at Startup and Reload | Supported | A YANG leaf can name a custom validator with ze:validate, and the named function runs on the same walk the offline ze config validate uses, so the offline check and the daemon cannot disagree about the same bytes. The set covers address families, community ranges, event and message type tokens, MAC addresses, IS-IS NET / system-id / hostname, OSPF router-id and area-id, redistribute sources, IPv4 and IPv6 addresses and prefixes, set references, port specs, and internal plugin names. A ze:validate binding with no registered function is an integrity failure reported at schema build, so a leaf cannot silently declare a validator that does not exist. |
| MCP Integration | Supported | AI-assisted BGP operations through Model Context Protocol. The transport is stateless Streamable HTTP, revision 2026-07-28: per-request _meta, standard request headers, per-request authentication, and server/discover. Ze is an OAuth 2.1 resource server. Background tasks are server-directed through the extension: the daemon reads each command's ze:task-support annotation and decides whether to return a task handle. The client polls a task, and the server never pushes one. MCP Apps UI resources carry embedded panels, and Ze gates them on the extension. Cacheable results carry ttlMs and a private cacheScope on server/discover, tools/list, resources/list and resources/read. Elicitation runs as a Multi Round-Trip Request. The server RETURNS resultType: "input_required" with an inputRequests map, in form mode. Ze sends this result only when the client declares form-mode support. The client then retries the original call with inputResponses, because the revision forbids a server to send an independent request on any stream. Ze mints no requestState, so Ze holds nothing between the two requests. The service is default-on and compile-out-able with the ze_mcp build tag. make ze-build and make ze-appliance-build include it, and ze-stripped and bare ze_core builds drop internal/component/mcp and its schema. An omitted MCP exposes no endpoint, and Ze rejects its environment { mcp {} } config block as unknown. . io.modelcontextprotocol/tasksio.modelcontextprotocol/ui |
| Chaos MCP | Supported | AI-queryable chaos test state via MCP: 6 tools (status, problems, peers, scenario, control, execute), Watchdog anomaly detector with structured PROBLEM lines, per-family convergence tracking |
| Chaos MRT Recording | Supported | --mrt-file flag produces standard BGP4MP_MESSAGE_AS4 and BGP4MP_STATE_CHANGE_AS4 MRT records from chaos peer events, readable by bgpdump/bgpkit-parser/ze-analyze. Strftime filename patterns for rotation. |
| MRT Dump | Supported | RFC 6396 daemon-side MRT recording with three independent streams (updates, all messages, periodic TABLE_DUMP_V2 RIB snapshots). YANG config, per-peer and direction filtering, extended timestamps, add-path aware, on-demand CLI dump, async non-blocking writes, strftime file rotation. Analysis tools: show, routes, aspath, inject, replay, convert (pcap/json), statistics, filter. The reader derives each record's AS width from the record type rather than from one file-wide guess, decodes the RFC 8050 add-path TABLE_DUMP_V2 and BGP4MP subtypes, and REPORTS what it could not decode instead of printing it as fact: a record that fails to parse renders [unparseable: <tag>] <err>, and every analysis subcommand ends with warning: N malformed MRT record(s) skipped or partially decoded; results are incomplete. The daemon MRT plugin is compile-out-able with the ze_mrt build tag (default-on in ZE_FEATURES): ze-stripped and bare ze_core builds drop internal/plugins/mrt and its config schema, and the internal/mrt format library plus the shared internal/core/bgp/msgtype leaf drop by dead-code elimination once neither MRT nor the BGP engine references them. MRT loads inert without a BGP source, so it is independent of ze_bgp; its reactor bridges reach the engine through a plugin-registry seam so the always-on hub never imports the plugin. |
| PKI Certificate Store | Supported | YANG pki {} config for CA certificates and device certificates with private keys. Base64-DER certificate parsing, PKCS8/SEC1/PKCS1 private key detection, $9$ sensitive encoding for keys, chain validation, expiry checking, atomic reload. PEM export for IPsec and TLS consumers. ServerTLSMaterial gives a TLS listener the leaf plus every stored intermediate, so the web/API HTTPS listener (environment.web.certificate) and the as112/geodns DoT/DoH listeners (tls { certificate }) can serve an operator certificate with its full chain; a configured name that does not resolve stops the listener rather than falling back to self-signed. Health check (degraded at 30 days, down when expired). Report bus warnings for approaching expiry. show pki certificates, show pki certificate <name>. Shared infrastructure for IPsec, TLS, and future mutual-auth features. |
| IPsec Data Model | Supported | YANG vpn { ipsec {} } config for site-to-site VPN: ESP groups (proposals, lifetime, PFS), IKE groups (proposals, DPD, key-exchange, close-action), site-to-site peers (X.509 and PSK auth, VTI bind, group references). Algorithm enums match strongSwan naming. Cross-reference validation (group names, PKI certificates, interface binding, local-id/CN match). Config diff detection for reload. |
| IKEv2 Wire Format | Supported | RFC 7296 wire codec: all payload types (SA, KE, Nonce, ID, AUTH, CERT, CERTREQ, Notify, Delete, Vendor, TSi/TSr, EAP, Configuration). Header encode/decode, payload chaining, encryption envelope. |
| IKEv2 Cryptographic Primitives | Supported | DH groups (MODP 2048/3072/4096/8192, ECP 256/384/521), PRF (SHA-256/384/512), integrity (HMAC-SHA-256/384/512), encryption (AES-CBC, AES-GCM-16 128/256, ChaCha20-Poly1305), SKEYSEED derivation, key expansion (RFC 7296 Section 2.14). |
| IKEv2 Engine | Supported | Full IKE FSM for both initiator and responder roles (connection-type initiate/respond): IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA, INFORMATIONAL exchanges. PSK and X.509 certificate authentication; as responder Ze also acts as the EAP authenticator (EAP-MSCHAPv2/EAP-TLS server). Child SA creation with traffic selectors and ESP proposals. IKE SA and Child SA rekeying (initiate and respond) with collision handling. DPD (Dead Peer Detection) via INFORMATIONAL exchange with configurable interval and timeout. XFRM policy and state programming via netlink. Reconciliation on config reload. SK crypto, key derivation, AUTH octets, and ESP key roles are parameterized by SA role. Compile-out-able with the ze_ike build tag (default-on in ZE_FEATURES): a stripped build drops the engine, IPsec config plumbing, crypto, EAP, wire codec, and command surface and rejects a vpn { ipsec {} } block; ike/dataplane (the XFRM programming seam OSPF's RFC 4552 authentication also uses) stays always-on. |
| IPsec EAP Authentication | Supported | EAP-MSCHAPv2 (type 26) and EAP-TLS (type 13) authentication for road warrior VPN clients inside IKEv2 IKE_AUTH exchange. MS-CHAPv2 crypto (NtPasswordHash, ChallengeResponse, MPPE key derivation), TLS handshake in EAP with fragmentation, MSK derivation feeding IKEv2 AUTH payload. Virtual IP pool with dual-stack allocation (IPv4 + IPv6), DNS push via Configuration Payload. |
| IPsec NAT Traversal | Supported | NAT detection via SHA-1 hash notify payloads in IKE_SA_INIT (RFC 7296 Section 2.23). Port 4500 with non-ESP marker for IKE, UDP encapsulation for ESP (RFC 3948). NAT keepalive (0xFF byte, 20s interval). XFRM SA UDP encap attribute set when NAT detected. |
| IPsec MOBIKE | Rejected | RFC 4555 MOBIKE notifications and XFRM endpoint migration are not implemented. |
| XFRM Interfaces | Supported | Route-based IPsec via XFRM interfaces (interface { xfrm <name> { if-id <N> } }). Traffic routed through the interface is encrypted; traffic arriving is decrypted. Created and deleted via netlink. |
| IPsec Interop Testing | Supported | Docker-based interop test infrastructure against strongSwan as the remote IKE peer, covering Ze as initiator (PSK, EAP-MSCHAPv2, EAP-TLS, BGP redistribute, child rekey) and as responder (PSK, EAP-MSCHAPv2, IKE-SA rekey) via. On hosts without XFRM (macOS Docker) the dataplane checks gate on availability and control-plane establishment is verified from strongSwan's logs. . test/ipsec-interop/scenarios/ |
| IPsec CLI and Diagnostics | Supported | show vpn ipsec sa/status/peer, clear vpn ipsec sa [peer <name>], monitor vpn ipsec (live SA event stream). Web page at with SA table. Health check (healthy/degraded/down from SA state). Prometheus metrics ze_ipsec_sa_count, ze_ipsec_tunnel_up and ze_ipsec_tunnel_degraded (per-peer gauges). Up requires an installed Child SA. Degraded names an established SA that carries no ESP. All show commands produce JSON and support full pipe operators. . /show/vpn/ipsec/ |
| DNS Resolver | Supported | Built-in cached DNS resolver for all components. Uses configured system.name-server or resolv.conf, and fails closed with no DNS server configured when neither is available. It does not silently fall back to public recursive resolvers. Cache management: show dns cache list/record, clear dns cache (flush/selective delete/stats reset). |
| Resolution CLI and Pipes | Supported | Offline ze resolve tool for DNS, Team Cymru ASN names, PeeringDB prefix counts, and IRR AS-SET expansion. The | resolve (reverse DNS) and | origin (ASN/network lookup via Team Cymru) pipe operators enrich any command's JSON output with DNS-based annotations. ze pipe brings these pipe operators to offline commands: ze show debug profile \| ze pipe match reactor, ze show debug profile \| ze pipe count, ze show debug profile \| ze pipe resolve. |
| Pipe Output Limiting | Supported | | first N and | last N pipe operators bound output to the first or last N items. Works on any command's JSON output (client-side truncation). Commands that register them as pipe filters (e.g. RIB) get server-side early termination: | first N stops the iterator at N, saving both iteration and serialization cost. | last N keeps a trailing window. JSON output includes a "pipe" metadata dict recording which data-shaping modifiers were applied. |
Stdin/stdout - on filename arguments |
Supported | Every filename-accepting command treats - as stdin when reading and stdout when writing, so configs and data stream through pipes without temp files: generate \| ze config show -, ssh host cat rib.mrt \| ze analyze show -, ze config show - \| ze config set - bgp session asn local 65001 \| ze config validate -, ze config migrate -o - old.conf. Resolution goes through one leaf helper so - behaves identically everywhere; MRT stdin auto-detects gzip/bzip2 by magic bytes. stdin is consumable once: a second - in one command fails closed. ze config edit/rollback/history - are rejected (no TTY / no revision history for a pipe). A build gate (make ze-dash-stdio-check) fails any command that reads/writes a user path without the helper. |
| Netdata-compatible OS Telemetry | Supported | 138 Prometheus metrics from /proc and /sys (CPU, memory, network, disk, IPv4/IPv6 protocols, conntrack, PSI, cpuidle, cpufreq, ZFS, btrfs, mdstat, SCTP, IPVS, wireless, etc) matching Netdata's naming and labels exactly. Drop-in replacement for Netdata's Prometheus exporter, existing Grafana dashboards keep working. Per-collector enable/disable, interval override, and prefix are scoped under telemetry.prometheus.netdata so Ze-native metrics keep their ze_* names. The Prometheus HTTP service defaults to loopback and can require HTTP Basic Auth. The Prometheus HTTP exporter (the /metrics + /health listener, telemetry config extraction, basic-auth, and the Netdata OS collectors) is compile-out-able with the ze_telemetry build tag: make ze-build and make ze-appliance-build include it, while ze-stripped and bare ze_core builds drop internal/component/telemetry/exporter and internal/component/telemetry/collector and reject the telemetry {} config block as unknown. Metric COLLECTION (the always-on internal/core/metrics registry used by ~60 packages, plus its no-op dummy) stays linked in every build, so a no-telemetry binary still records every ze_* counter; it just cannot expose them over HTTP. |
| Flow Export | Experimental | Interface counter and per-flow record export over UDP via sFlow v5, NetFlow v9 (RFC 3954), and IPFIX (RFC 7011). Per-collector polling interval and template refresh. Packet sampling (tc sample + psample) exported as sFlow flow samples with configurable 1-in-N rate, header truncation, and psample group. Conntrack-based per-flow records (periodic table dumps) for NetFlow v9 and IPFIX. Optional BGP next-hop enrichment from the RIB best-change event. show flow export [<collector>] reports per-collector datagrams-sent, bytes-sent, errors, sequence, and last-export-time. show flow recent [dst <prefix>] returns recent conntrack flow records (5-tuple + TCP state) from a bounded drop-oldest ring (recent-flow-ring, default 4096 records, allocated only when conntrack export is on) that feeds on-box DDoS characterization. Prometheus metrics: ze_flowexport_datagrams_total, ze_flowexport_bytes_total, ze_flowexport_errors_total, ze_flowexport_samples_total, ze_flowexport_flows_total, ze_flowexport_flows_active, ze_flowexport_recent_ring_drops. Per-flow records cover IPv4 and IPv6 (separate templates); BGP enrichment currently fills next-hop only; sampling needs Linux with CAP_NET_ADMIN and kernel psample. Compile-out-able with the ze_flowexport build tag: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds, with its config block rejected as unknown. |
| Traffic Monitor | Experimental | Lazy consumer-refcounted aggregation service (internal/component/trafficstat) that subscribes to the observation feed and maintains a time-windowed ranked usage view (per-interface rates, top-N source/dest IPs, top ports with service names, protocol mix, 60s history). The aggregator is rebuilt on the shared internal/core/stats rolling-window primitive; severity is now a display-only CLI computation from the history facts (the neutral layer holds no verdict). Runs only while consumers are attached. Consumed by show traffic stat (one-shot JSON snapshot), monitor traffic stat (full-screen alt-screen TUI via the generic MonitorProvider registry), and ddos/detect (Depth-1: pre-computed per-interface rates instead of raw counter diffing). Includes internal/core/portname hardcoded port-to-service-name table with amplification-vector overlay for 7 known reflection ports. |
| Traffic Feature Signals | Experimental | Neutral per-source detection SIGNALS (facts, never verdicts) derived from the observation feed by a second consumer alongside the traffic monitor: fan-out (distinct destinations), out/in byte ratio (exfiltration), destination-port entropy, new-peer, rare-port/proto, and coarse beaconing (interval regularity, bounded to periods of a few seconds by the 1s sampling tick). Computed via the shared internal/core/stats primitives (rolling window, Shannon entropy, interval regularity). Viewed via show traffic feature; bounded per-source state with idle eviction. The judgment layer (the anomaly detection family) consumes these facts. |
| Volumetric DDoS Detection and Mitigation | Experimental | The volumetric domain, kept separate from the behavioral anomaly detector above. ddos-detect runs two-stage detection over the traffic-monitor feed: per-source packet-rate and bandwidth p99 baselines, attack characterization, and incident confidence scoring. Three responders consume its incidents. ddos-local drops box-directed attacks with an on-host nft rule. ddos-flowspec originates a FlowSpec or RTBH rule upstream for transit attacks. ddos-flowtriq reports incidents to the Flowtriq cloud API. ddos-observe holds the incident lifecycle behind show ddos status and show ddos incidents. Clearing an upstream mitigation is the hard half, because an upstream drop blinds the box's own sensors: the leak probe narrows the announced rule to probe-rate and reads the detector's attack-ongoing bandwidth to see whether the flood still arrives. An announce is also withdrawn by max-mitigation-duration (checked once a second, 0 means no cap, default 3600s, range 0..604800), by a characterized exemption, and by a victim that reclassifies from remote to local. max-mitigation-duration is enforced by ddos-flowspec only. The identically named leaf under ddos local parses, defaults and range-checks but nothing reads it, so an on-host drop carries no time cap today. |
| Behavioral Anomaly Detection | Experimental | Darktrace-style SECURITY anomaly detector (report-only), a domain separate from volumetric DDoS. Consumes the neutral trafficfeature signals and learns each source entity's own pattern-of-life via a per-(entity,feature) EWMA baseline (internal/core/stats), scores self-deviation plus peer-group rarity (source-prefix cohorts), and correlates multiple weak feature deviations on one entity into a single incident (capped/discounted combine, not naive sum). A confirm/clear state machine debounces; confirmed incidents emit on the anomaly-detect event bus and land in a bounded recent-incident ring viewed via show anomaly detect. It takes NO action (the anomaly/shape responder acts); scoring is bounded per-entity with idle eviction. A ze doctor check (doctor-anomaly-detect-no-feature-source) warns when enabled without a flow source. Prometheus: ze_anomaly_incidents_total, ze_anomaly_active, ze_anomaly_tracked_entities. Both the detector and the shape responder are compile-out-able with the ze_anomaly build tag: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds, with an anomaly {} block rejected as unknown. |
| Autonomous Anomaly Response | Experimental | Shadow-first responder (anomaly/shape) that subscribes to anomaly-detect incidents and installs a surgical per-SOURCE firewall action (rate-limit via firewall.MatchSourceAddress+Limit, drop fallback). SHADOW is the default: it logs the would-be action and installs nothing. In armed mode each anomalous source gets its own live nft rule with a mandatory timed AUTO-REVERT (withdraws after a TTL regardless of any clear event), a global BLAST-RADIUS cap (refuses to arm beyond N), a KILL-SWITCH (reverts all + forces shadow), and an ALLOWLIST (protected sources are never armed). One mutex guards the armed map; a per-timer generation guard makes a superseded timer a no-op. Status via show anomaly shape. Separate firewall owner key isolates it from ddos/local. A ze doctor check (doctor-anomaly-shape-armed-no-firewall) warns when armed without a firewall. Prometheus: ze_anomaly_shape_armed, ze_anomaly_shape_reverted_total, ze_anomaly_shape_arm_refused_total, ze_anomaly_shape_killswitch_total. |
| Traffic Usage | Experimental | eBPF TCX per-(port, protocol) and opt-in per-IP byte accounting on operator-selected interfaces, exported as Prometheus metrics and viewed via show traffic usage [name <interface>]. IPv4 only, monitoring only (never drops or modifies traffic), Linux >= 6.6 (no-op elsewhere; needs CAP_BPF + CAP_NET_ADMIN). The eBPF programs are assembled in pure Go (cilium/ebpf asm.Instructions) and loaded from memory: no C source, no committed .o, no clang/LLVM; hand-written assembly validated by BPF_PROG_TEST_RUN tests. Per-port accounting (ingress by dst_port, egress by src_port) is always on; track-ip adds per source/destination IPv4 (off by default to bound cardinality). Configurable poll interval, stale-timeout (delete unseen series to bound /metrics cardinality), and per-map LRU max-entries (top-talker eviction). Prometheus metrics: ze_traffic_usage_ingress_port_bytes_total, ze_traffic_usage_egress_port_bytes_total, ze_traffic_usage_ingress_bytes_total and ze_traffic_usage_egress_bytes_total (track-ip only), ze_traffic_usage_map_entries. A ze doctor check (doctor-traffic-usage-ebpf) warns when enabled but eBPF/TCX is unavailable. Compile-out-able with the ze_trafficusage build tag: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds, with its config block rejected as unknown. |
| VPP Data Plane | Experimental | Manages VPP lifecycle (startup, crash recovery, DPDK NIC binding), programs FIB routes via GoVPP binary API, and polls VPP's stats segment for per-interface, per-node, and system-wide Prometheus metrics. Configurable poll interval. MPLS label operations (push, swap, pop) driven directly from BGP labeled unicast (RFC 8277): labels are stripped at NLRI parse, stored as RIB side-data, propagated through best-change events, and programmed into VPP via GoVPP IPRouteAddDel with LabelStack (push) or MplsRouteAddDel (swap/pop). vpp.external=true switches ze into connect-only mode for systemd-managed / container-sidecar deployments and the ze-test vpp stub harness. Local Docker evidence via make ze-deployment-vpp-test covers real VPP FIB add/withdraw, traffic policer apply/bind, same-config Ze restart preservation, startup cleanup of stale Ze traffic policers, and the IKE IPsec dataplane: two SAs and two policies programmed over the VPP binary API and read back through vppctl (SPIs, the inbound flag, the AEAD key and salt in their own fields, the ECN copy flags RFC 7296 Section 2.24 requires, the SPD bound to the interface, and both policies in priority order), plus proof that closing the backend removes the SAs and the SPD it installed so a restart leaves no orphan state. The IKE IPsec backend is TEST-ONLY and cannot yet be driven by IKE: it REFUSES every policy the IKE engine produces, because a VPP policy lives in an SPD bound to named interfaces and nothing supplies that interface; no config leaf selects the backend (only the private ze.test.ike.dataplane override does); ListSAs, ListPolicies and RemovePolicy all refuse, so an operator cannot read back or surgically remove what it programmed; and no test has sent ESP through it, so the evidence above is what VPP accepted and holds rather than traffic it carried. Same-process VPP traffic reapply now replays policer output binding so VPP-side unbinds converge back to desired state. Host tuning: vpp.cpu.poll-sleep (Nms) exposes VPP's idle main-loop sleep (unix { poll-sleep-usec }, unset = busy-poll for lowest latency); on the gokrazy appliance image.hugepages { size, page-size } reserves hugepages at boot by baking hugepages= kernel arguments, and ze doctor reports doctor-vpp-hugepages when the reservation is missing/insufficient/clamped. Compile-out-able with the ze_vpp build tag (default-on in ZE_FEATURES): a netlink-only build drops the GoVPP connector component, every per-plugin VPP backend (fib, firewall, iface, traffic, static, IKE IPsec), and the vendored GoVPP library; a vpp {} block is rejected as unknown and selecting a vpp backend fails closed with an unknown-backend error naming the registered set. |
| L2TPv2 BNG | ∿ | RFC 2661 L2TPv2 LNS/LAC with full BNG stack: tunnel lifecycle (SCCRQ/SCCRP/SCCCN, CHAP-MD5 challenge, HELLO keepalive, StopCCN teardown, tie-breaker), configurable dead-peer detection (hello-retries x hello-interval ACK-driven keepalive timeout, separate from the ~31s reliable-transport retransmit exhaustion, so a peer that dies without StopCCN is torn down fast while an idle-but-alive peer that only ZLB-ACKs HELLOs is not falsely dropped) , reliable delivery with sliding window and congestion avoidance, PPP negotiation (LCP 10-state FSM, PAP/CHAP-MD5/MS-CHAPv2 auth, IPCP/IPv6CP address assignment, proxy LCP), kernel data plane via l2tp_ppp netlink and PPPoL2TP sockets. Four plugins: auth-local (static users), auth-radius (RADIUS auth/acct/CoA/DM with failover, real per-subscriber traffic counters in Interim/Stop including RFC 2869 Gigawords), pool (bitmap-backed IPv4 ranges from Ze config), shaper (TC TBF/HTB on pppN interfaces with configured defaults and CoA rate updates). Subscriber route redistribution via redistribute { import l2tp }: the real RouteObserver emits add/remove route-change batches, and BGP announce/withdraw plumbing is covered with a synthetic producer. CQM monitoring (100s echo RTT/loss buckets, per-login sample rings, 24h retention). Web UI at /l2tp with session list, detail page, uPlot CQM graph with SSE live updates, event timeline, and disconnect with audit trail. ze_l2tp_* Prometheus metrics (session/tunnel gauges, per-session byte/packet counters, CQM echo RTT histogram, loss ratio, bucket state). ze_radius_* metrics (auth/acct/interim counters, server reachability). RADIUS Access-Accept subscriber profile attributes are consumed: Framed-IP-Address bypasses pool (direct IP assignment), Framed-Pool selects a named pool, Session-Timeout/Idle-Timeout enforce session lifetime, Filter-Id sets initial shaping rate at session establishment, Vendor-Specific attributes (RFC 2865 S5.26) extract CoS profiles from Cisco-AVPair/Juniper ERX/Nokia/Huawei VSAs and shaper rates from MikroTik Mikrotik-Rate-Limit (Ze "cos:" Filter-Id takes priority; unknown vendors silently ignored), Acct-Interim-Interval overrides the per-session accounting interval, and Framed-Route/Framed-IPv6-Route (RFC 2865 Section 5.22, RFC 6911 Section 3.2) inject per-subscriber static routes into BGP alongside the subscriber /32 or /128. Named pools are configured via YANG named-pool list under l2tp > pool. PPP auth now defaults to mandatory CHAP-MD5 with finite tunnel/session caps; no-auth requires explicit opt-in, hidden mandatory AVPs are rejected fail-closed. Every Accounting-Request carries Framed-IP-Address, the address IPCP actually assigned (RFC 2866 Section 4.1), and a NAS-Port-Id composed once per session from a {nas-id} / {tunnel-id} / {session-id} template (nas-port-id-format, RFC 2869 Section 5.17), so an LNS with no physical port still joins its accounting records. An SCCRQ whose Assigned Tunnel ID AVP carries 0 is a protocol error under RFC 2661 Section 4.4.3: Ze answers it with a StopCCN sent outside any tunnel, allocates no tunnel, and bounds the reply to one per source-address slot per second so a spoofed flood costs nothing. Docker-backed xl2tpd evidence covers external LAC control tunnel and incoming-call session setup. A peer-isolated Docker lab (make ze-deployment-docker-l2tp-ppp-test) proves full PPP LCP/IPCP, kernel pppN creation, dataplane ping, and BGP route redistribution from a live PPP session with Ze LNS, real xl2tpd/pppd LAC, and FRR in separate containers; requires host kernel PPPoL2TP support. Compile-out-able with the ze_l2tp build tag (default-on in ZE_FEATURES): a stripped build drops the whole BNG subtree (L2TP, PPPoE, subscriber management, local-auth/pool/shaper plugins) and rejects l2tp {} and pppoe {} blocks; the hub builds the subsystems through the gated bngRegister seam, the web session pages and diag l2tp captures answer not-in-this-build, and the cos dynamic RADIUS-CoS handler drops with it. |
| TACACS+ AAA | ∿ | RFC 8907 TACACS+ client for SSH login: PAP authentication, ordered server failover with per-server timeout, MD5 pseudo-pad body encryption, priv-lvl-to-profile mapping, command accounting (START/STOP records on every dispatched CLI command), and explicit-reject vs unreachable distinction so wrong-password TACACS+ replies do NOT silently fall through to local bcrypt. Runs as a pluggable aaa.Authenticator so local bcrypt remains the fallback when every TACACS+ server is unreachable (default). Configurable strict-fallback mode denies authorization when TACACS+ infrastructure is unavailable instead of falling back to local RBAC. |
| RADIUS admin AAA | ∿ | RFC 2865 RADIUS authentication for operator/admin login (SSH, web, MCP), distinct from the L2TP subscriber RADIUS path. PAP (User-Password hidden per §5.2), ordered server failover with per-server timeout and retries, Access-Accept reply-attribute-to-profile mapping (configurable, default Filter-Id §5.11, or Class §5.25) with a default-profile fallback, an Accept resolving to no profile rejected rather than authorized (an authenticated login always names at least one profile), and explicit-reject vs unreachable distinction so a wrong-password Access-Reject does NOT fall through to local bcrypt while an unreachable server does. Runs as a pluggable aaa.Authenticator at chain priority 50 (ahead of TACACS+ 100 and local 200); local bcrypt remains the fallback. ze doctor probes reachability (doctor-radius-admin-unreachable). Reuses the L2TP RADIUS client, so retransmit, failover, and Response-Authenticator verification behave identically. CHAP/EAP admin auth and admin-session accounting are not implemented. Compile-out-able with the ze_radius build tag (default-on in ZE_FEATURES); the l2tp RADIUS auth plugin is the dependent piece, generated behind ze_l2tp && ze_radius, so a radius-less BNG build links zero RADIUS symbols and a bng-less build keeps RADIUS system authentication. |
| PPPoE Access | ∿ | RFC 2516 PPPoE access concentrator: discovery state machine (PADI/PADO/PADR/PADS/PADT), HMAC-SHA256 AC-Cookie for DoS protection, per-interface session tables with bitmap SID allocation (1-65535), per-source-MAC PADI rate limiting, Service-Name filtering, kernel PPPoE sessions via AF_PPPOX + PX_PROTO_OE, and integration with the transport-agnostic PPP Driver (same auth/pool/shaper plugins as L2TP). Subscriber authentication is per-AC: auth-method selects none, pap, chap-md5 (the default) or ms-chap-v2, and the AC advertises it in its own LCP Configure-Request; allow-no-auth is the explicit opt-in that auth-method none requires, so an unauthenticated concentrator is never the default. The credential is verified by the same l2tp-auth-local or l2tp-auth-radius handler L2TP uses. YANG config (pppoe {}) with per-interface settings. CLI commands: show pppoe, show pppoe sessions, show pppoe statistics, show pppoe interfaces. Runs concurrently with L2TP on the same daemon. Functional tests (make ze-qemu-pppoe-test) drive a real client over a veth pair inside a per-test network namespace on ze's runtime kernel: PADI to PADO carrying AC-Name and AC-Cookie, PADR to PADS with a kernel AF_PPPOX session and a non-zero session id, a forged AC-Cookie earning no PADS, the same exchange on an 802.1Q sub-interface, and an L2TP SCCRP answered while the AC is bound. Both roles are now interop-tested in: scenario 01 runs Ze as a client against accel-ppp, and scenario 02 runs Ze as the access concentrator with pppd 2.5.1 and the rp-pppoe plugin as the client, asserting discovery, LCP, CHAP-MD5 accept and reject, IPCP address assignment from the pool, ICMP across the session, and a PADT teardown that empties Ze's session table read over its own REST API. Partial because PADI rate limiting and Service-Name filtering still have unit coverage only. PPPoE ships inside the ze_l2tp build tag (the BNG gate); a stripped build rejects a pppoe {} block as unknown. . test/pppoe/test/pppoe-interop/ |
| Firewall | Experimental | Packet filter and NAT via the nftables backend on Linux. Abstract model supports 15 match types (source/destination address, port ranges, protocol, input/output interface with wildcard prefix, connection state, marks, DSCP, ICMP type, ICMPv6 type, TCP flags, named sets) and 19 action/modifier types (accept, drop, reject, jump/goto/return, SNAT/DNAT with address ranges, masquerade, redirect, notrack, flow offload, set mark/connmark/DSCP/TCP-MSS, counter, log, rate limit). NAT exclude rules emit a Return verdict to skip translation. Global-options container maps keyword toggles (all-ping, broadcast-ping, syn-cookies, source-validation, etc.) to kernel sysctls via the sysctl plugin's default layer; explicit sysctl settings always override. IRR-based prefix-list filtering: source-asn, source-as-set, destination-asn, destination-as-set leaves in from-block resolve ASN/AS-SET references to nftables interval sets via the shared IRR PrefixStore (firewall-irr plugin). Per-interface source validation: bind an AS-SET to a customer-facing interface via irr { interface <name> { source-as-set <AS-SET>; } } to drop ingress traffic with source addresses not in the IRR-resolved prefix set (BCP 38). Component reactor wires into ze's engine lifecycle: Apply on boot and reload, rollback on failure. ze_ prefix on all kernel tables. Every backend Apply is bounded by a deadline: 10s by default, clamped to 1s..60s (ze.firewall.nft.netlink-timeout, ceiling firewall.MaxBackendDeadline). The bound is load-bearing rather than cosmetic, because ApplyAll holds one process-wide reconcile lock across Backend.Apply, so an unbounded kernel call would stall every firewall owner (copp, policy-routes, ddos-local, the firewall engine) and not just one. A wedged reconcile is counted, never hidden: ze_firewall_apply_timeout_total, and ze_firewall_apply_duration_seconds{result} with ok, timeout, error and panic. |
| Control-Plane Policing (CoPP) | Experimental | Rate-limit new TCP connections to the BGP listen port (TCP/179) to protect against connection-flood DDoS. Generates an nft input-hook chain via the firewall registry: established/related sessions pass at full rate, operator-supplied trusted-source prefixes bypass the limit, new connections are rate-limited. Configurable rate, burst, protected-port (default 179), trusted-source prefix list, and over-limit policy (accept/drop, default accept for lock-out safety). Doctor check verifies the CoPP input chain is active when configured. Compile-out-able with the ze_copp build tag: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds, with its config block rejected as unknown. |
| VPP Firewall Backend | Stub-backed | Registered as firewall { backend vpp }. Filter chains translate ze Match/Action types to VPP ACL rules via GoVPP binapi (source/destination prefix, port range, protocol, ICMP type/code, TCP flags, permit/deny/reflect). Connection-state established,related maps to ACL_ACTION_API_PERMIT_REFLECT (VPP reflexive ACL). NAT chains configure VPP NAT44-ED: masquerade via output-interface mode, SNAT via address pool + inside interface feature, DNAT via static mappings with tagged cleanup. SetMark and Limit actions use VPP's classify pipeline: classify tables match traffic by packet header fields, SetMark sets opaque metadata via CLASSIFY_API_ACTION_SET_METADATA, Limit creates a policer bound to the classify table via PolicerClassifySetInterface. Expression types without a faithful VPP representation are rejected at commit via firewall.RegisterVerifier("vpp", Verify): interface matches, connection marks, DSCP, sets, packet modification (connmark/dscp/tcp-mss), counters, log, chain traversal. |
| Commit-Time Backend Capability Check | Supported | YANG nodes that correspond to backend-specific features carry a ze:backend "<names>" annotation. On commit (daemon reload, first-apply, and ze config validate), the walker rejects the config with the YANG path and the list of supporting backends whenever the active backend does not implement the feature -- instead of letting an Apply-time "not supported" error fire inside the backend. The gate covers interface (netlink-only bridge, tunnel, wireguard, veth, mirror under the vpp backend), traffic/control (the backend-leaf CALL wired into OnConfigure/OnConfigVerify; tc-only feature annotations ship with spec-fw-7-traffic-vpp), and firewall (seven ze:backend "nft" annotations on conntrack-driven matches and nft-only action/modifier leaves). |
| Backend-Aware CLI Completion | Supported | CLI auto-completion filters options based on the active backend. Config editor mode (set/delete/edit/show) and operational command mode (show/clear/monitor) hide nodes annotated with ze:backend when the active backend is not in the annotation's list. Backend names are derived from the config tree at each tree change. Same ze:backend annotations used by commit-time validation, applied earlier at completion time. |
| Traffic Control Lifecycle | Experimental | The traffic/control section of the config is now programmed at boot and on SIGHUP reload. The traffic component's reactor calls the selected backend's Apply(map[string]InterfaceQoS) in OnConfigure and OnConfigApply, with sdk.Journal rollback on apply failure. Linux default backend is tc (netlink); future backends plug in via traffic.RegisterBackend. Local privileged integration covers netlink qdisc snapshot/restore after backend restart; target-runner and reactor-level boot/reload kernel-state evidence remain open. |
| VPP Traffic Control Backend | Stub-backed | Registered as traffic { control { backend vpp } }. Scope is per-class policing: HTB and TBF qdiscs translate each class to a VPP policer (CIR = Rate, EIR = Ceil, kbps with round-up). An unfiltered single class binds to interface egress via PolicerOutput. A class carrying a steering filter -- filter protocol <n> or filter dscp <v> -- is instead bound to the ingress policer-classify feature: the backend builds per-family (IPv4 + IPv6) classify tables whose mask/match vectors match the packet at absolute frame offsets and steers matching packets to the class policer via HitNextIndex (byte-for-byte VPP's own policer-hit-next, verified on VPP v25.10). Offsets: IPv4 protocol byte 23 / IPv6 next-header byte 20 for filter protocol; IPv4 TOS byte 15 (mask 0xFC) / IPv6 traffic-class bytes 14-15 (masks 0x0F/0xC0) for filter dscp -- filter dscp polices the DSCP-matched traffic (police-by-dscp), it is NOT a QoS remark. Multi-class HTB/TBF is accepted when EVERY class carries a steering filter: per-interface, per-family the sessions are grouped by field mask into one table per distinct mask (a session per class -> its own policer), and distinct-mask tables are chained via ClassifyAddDelTable.NextTableIndex (real VPP v25.10 validated the multi-session steering and chain fall-through). A multi-class config with any unfiltered class, the prio qdisc (a scheduler VPP does not expose), mark filters (Linux SKB fwmark has no faithful VPP equivalent), and every other qdisc type are rejected at OnConfigVerify via traffic.RegisterVerifier("vpp", Verify). Per, shipping silent-no-op features (classify sessions in a detached table, or N unfiltered policers stacked on the output feature arc producing min(rates) instead of per-class shaping) is banned. Apply waits up to 5s for VPP to be reachable and returns vpp not connected after 5s on timeout. On partial-apply error the backend undoes what this call programmed in VPP before returning. Reconcile-time deletions are tolerant of stale indexes (post-VPP-restart): failures log a warning and continue instead of failing the commit. A fresh backend instance scans VPP policers with the ze/ prefix, removes undesired startup orphans, and rebinds desired policers from config. Same-process reapply also replays PolicerOutput(apply=true) for existing desired policers, repairing VPP-side output unbinds without requiring a Ze restart. Protocol-filter classify tables are reclaimed by in-process reconcile (unbind + delete by tracked index); because VPP classify tables are anonymous they cannot be reclaimed by name at startup like policers, so a table left by a previous process is inert (unbound) memory reclaimed only by a VPP restart. Real-daemon traffic-control evidence covers policer apply/bind, restart preservation, startup orphan cleanup, protocol-filter and dscp-filter classify-table attachment + reconcile removal, and multi-class per-class steering (two policers bound via one table per family). . rules/exact-or-reject.md |
| RFC 1997 well-known community suppression | Supported | NO_EXPORT, NO_ADVERTISE and NO_EXPORT_SUBCONFED are honored on egress automatically, on both forward rails, with no configuration and no switch. The scan runs once per UPDATE over the SOURCE payload the peer sent, never over a policy chain's wire override, so an export policy that strips NO_EXPORT does not license the leak: the route arrived carrying it. A destination the gate refuses receives the withdrawal half of that same UPDATE rather than nothing, so a route already advertised is taken back. This is the ordering that separates Ze from FRR and BIRD, which apply the route-map first, where stripping the community there restores advertisement. |
| RFC 7999 BLACKHOLE | Supported | Per-session honoring of the BLACKHOLE community, off by default. The peer names the community it uses (blackhole communities, the well-known 65535:666 or its own) and the prefixes it is authorized to blackhole within (blackhole prefixes); RFC 7999 Section 3.3 states both conditions, so a peer with a community and no covering prefix blackholes nothing. A honored route becomes a discard route directly: a Linux FIB blackhole or a VPP drop, with no next-hop to allocate and no static route to pre-create. The same list governs the send side, which is RFC 7999 Section 3.1's requirement that the two networks agree before the community is advertised: announce blackhole and announce unicast <prefix> community 65535:666 reach only the sessions that agreed, and a session that named its own value alone is left out rather than sent the prefix untagged. The leaves are augmented at the bgp, group and peer levels. |
| Egress attribute conformance (RFC 4271 Section 5) | Supported | Each rule is enforced at one site and applies on every rail that writes an UPDATE (announce, forward, route server): LOCAL_PREF is never sent on an external session (5.1.5); a received MULTI_EXIT_DISC is not relayed to another neighboring AS (5.1.4), with the route-server client exempt per RFC 7947 Section 2.2.3; no peer is sent its own address as NEXT_HOP (5.1.3); the Partial bit is set on an unrecognized transitive optional attribute (Sections 5 and 9); withdrawals are applied before announces (4.3); a relayed withdrawal creates no attribute (4.3 and 6.3); and attributes are inserted by ascending type code with MP_UNREACH first. |
| Path Identifier regeneration (RFC 7911) | Supported | Ze generates its own Path Identifier for every route it re-advertises rather than relaying the one the source chose. The identifier is keyed on the ingress path, which is (source peer, received identifier), so the same path always gets the same value and two route-server clients that happened to pick the same identifier no longer collide at a third. A received identifier of 0 is a value and not an absence: RFC 7911 Section 3 makes it legal, and a source that negotiated no ADD-PATH sends every path under it. The table tracks every identifier currently assigned, so a wrapped counter cannot hand a live path's identifier to a second path. |
| Typed-family NLRI discard (RFC 7606 Section 5.4) | Supported | Answered per family by the plugin that owns it, because Section 5.4 leaves the ruling to the family's own document. EVPN, MCAST-VPN and MUP discard an unrecognized route type. BGP-LS does not: an unrecognized NLRI type is its common case, and RFC 9552 Section 5.1 requires those to be preserved and propagated rather than treated as an error, so only that NLRI is reported unparsed and decoding continues. |
| Prefix-limit counting mode | Supported | Per-family prefix { count offered \| installed; }, default offered. offered counts announcement events off the wire; installed counts the set of prefixes the family currently holds, so a peer that re-announces the same prefix does not walk toward its limit. |
| Dynamic peer groups | Supported | A group whose connection/remote/ip is dynamic accepts a session from any address inside its range list and opens its own listening socket, so a configuration naming no static peer still accepts members. Each accepted connection becomes one peer that inherits the group's whole resolved settings, its families, its attach process blocks and its per-peer plugin config. Overlapping ranges resolve by longest prefix match. max-peers bounds the group (1..100000, default 1000). |
| AIGP (RFC 7311) | Supported | Accumulated IGP Metric path attribute. Capability negotiation, wire encoding/decoding, structured JSON exposure with RFC 4271 attribute flags. Not consumed in best-path selection. |
| PATHS-LIMIT (draft-abraitis-idr-addpath-paths-limit) | Supported | Per-family path count limit for ADD-PATH. Receiver advertises max paths per prefix; sender enforces. Unified config under capability { add-path { limit N; } } with per-family overrides. RS fast-path peers suppress the capability. |
| RPKI ASPA Policy Enforcement | Supported | ASPA path verification (draft-ietf-sidrops-aspa-verification) with configurable policy enforcement: rpki/aspa/action/invalid supports reject, log-only, accept. ASPA records distributed via RTR v2 (RFC 9582). |
| RPKI Per-Peer Action | Supported | Origin (invalid/not-found) and ASPA (invalid/unknown) validation actions are settable globally and per-peer/per-group (peer > group > global, resolved per leaf), while cache servers and ASPA enable stay global. rpki { blackhole-exempt true; } exempts a session's agreed blackhole routes from origin validation, at the peer and group levels; setting it on a session that names no blackhole community logs that it has no effect rather than passing silently. show bgp rpki status reports the effective global and per-peer resolved actions, and separates two states an operator could not otherwise see: sessions-synced counts the caches that have completed a sync, and synced is false while running is true when none has. The RTR client separates the RFC 8210 Section 6 Refresh Interval from the Retry Interval, so a cache that answered and a cache that failed are not polled on one timer. A VRP change re-validates the routes already installed rather than only the ones that arrive next, per RFC 6811 Section 4, and the ASPA cache does the same for path verification. |
| IRR BGP Import Filtering | Supported | bgp-filter-irr builds per-ASN import prefix-lists from explicit or PeeringDB-discovered IRR AS-SETs, refreshes and persists the lists, and rejects received prefixes outside the selected list. Includes status, prefix inspection, dry-run checks, and manual refresh commands. |
| FlowSpec-to-Firewall Bridge | Supported | flowspec-firewall plugin converts BGP FlowSpec rules into nftables entries in a dedicated ze_flowspec table. |
| IXP Route Server Dynamic Peers | Supported | Route server (bgp-rs) supports dynamic peers for IXP deployments. Peers connect dynamically and inherit configuration from a peer group template. RS-client role and per-peer community filtering. |
| Subscriber Session Model | Supported | Unified subscriber session model for L2TP/PPPoE subscribers with shared session lifecycle, auth, pool, and shaper infrastructure. Show enricher registry lets plugins contribute data to show commands: in-process plugins register via show.MustRegister in init; external plugins declare enrichers at registration (Stage 1) and handle ze-plugin-callback:enrich-show callbacks at runtime with a 2s timeout. Web service-locator pages call show.Enrich explicitly. show subscriber detail and show subscriber gain CoS profile data when the cos plugin is loaded. . internal/core/show/ |
| Config Schema Stamp | Supported | Config files carry a schema version stamp. Downgrade recovery prunes incompatible fields when loading a config from a newer version. |
| Config Dependency Graph | Supported | ze config graph visualizes config dependency relationships. |
| Graceful Listener Migration | Supported | Hot reload of listener endpoints (web, LG, REST, gRPC, MCP). New listener starts before old one stops; in-flight connections are drained. |
| Docker Support | Supported | Two images, one binary. make ze-docker-build builds the deployment image: a static binary on a scratch base, 119 MB, ENTRYPOINT /ze. make ze-docker-lab-build builds netlab/ze:latest for netlab and containerlab: tini and iproute2, 137 MB, because those tools run sh and ip inside a node. Both recipes derive their build tags from feature-gates.txt, so a defect cannot reproduce in a lab and disappear in production. ZE_TAGS adds tags to that set instead of replacing it. Compose support via. . alpine:3.21docker/compose.yaml |
| netlab device | Experimental | Ze runs as a netlab daemon device under containerlab. mirrors what netlab installs: the daemon definition, the Jinja2 templates, a reference topology, and the committed render. The templates render the whole running configuration into. Declared modules are bgp, ospf, isis, bfd, and routing. Ze reloads on SIGHUP, and netlab sends none, so ze.yml declares initial.reload: false. A configuration change in a running lab needs a manual signal or a node restart. netlab validate reads ze cli -c "show. \| json compact" through docker exec. make ze-netlab-render-check renders the templates with a real netlab and compares the result against the golden files. starts a daemon from one of them. A live lab was not started, so netlab up and netlab validate are unrun. The declared features are rendered and parsed, never proven against netlab's integration tests. No LLDP. . contrib/netlab//etc/ze/ze.conftest/plugin/netlab-lab-profile.ci |
| Archive Pruning | Supported | commit-revisions config field limits the number of retained committed revisions. Older revisions pruned after each commit. |
| DHCP Server Named Ranges | Supported | Multiple named address ranges per subnet for segmented allocation. Each range has an independent bitmap pool. Compile-out-able with the ze_dhcpserver build tag: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds, with its config block rejected as unknown. |
| GeoDNS | Experimental | Authoritative DNS server that selects the answer from the client's source IP, taken from the EDNS0 client-subnet option (RFC 7871) or the connecting address. Zones, host sets, per-source selection, and a configurable SOA (contact, serial mode, refresh, retry, expire, minimum) with the serial recomputed per generation. Optional DoT (RFC 7858) and DoH (RFC 8484), whose certificate is either a cert-file/key-file pair or a tls { certificate <name> } reference into the pki {} store; the two forms are mutually exclusive. Shares the answer-policy harness described below with as112. |
| Authoritative DNS Answer Policy | Supported | One harness serves as112 and geodns, so both answer a malformed or out-of-scope query the same way. A name outside every served zone gets REFUSED with the AA bit CLEAR, because RFC 1035 Section 4.1.1 gives AA one meaning and a responder that keeps it set on a Refused reply asserts an authority it does not have. A name inside a served zone that the zone does not own gets NXDOMAIN. A name that exists with no record of the requested type gets NOERROR with an empty Answer. Both negative answers carry the zone SOA in the Authority section, which RFC 2308 Section 3 requires so a resolver can cache them. A non-query opcode gets NOTIMP. A UDP reply is bounded to 512 octets (RFC 1035 Sections 2.3.4 and 4.2.1) or to the larger buffer the requestor advertised in an OPT record (RFC 6891 Section 6.2.3), with TC set; an advertisement below 512 does not lower the bound. TCP, DoT and DoH are unbounded. |
| AS112 Anycast DNS | Supported | Authoritative sink for misdirected RFC 1918 / link-local reverse-DNS queries (RFC 7534) and the EMPTY.AS112.ARPA DNAME-redirection zone (RFC 7535). Four fixed anycast host addresses (never operator-typed), registered against the iface address-ownership registry and bound via IP_FREEBIND. Optional allow-from client-source access list (loopback always permitted). BGP integration composes existing healthcheck/watchdog/update-block mechanisms: the anycast route announces only once a probe against a real anycast service address confirms the DNS service is healthy, with an operator-chosen community and optional AS112-origin AS_PATH override. show as112, as112 health [target <ip>], ze_as112_* metrics. The four covering prefixes can also be BGP-originated directly through the redistribute path (redistribute { destination bgp { import as112 } }) with a configurable origin ASN (asn, default 112) and community, health-gated by the watchdog serving-state check (RFC 7534 Section 3.3). Compile-out-able with the ze_as112 build tag: default-on in ZE_FEATURES, dropped from ze-stripped / bare ze_core builds, with its config block rejected as unknown. |
| ExaBGP Compatibility | Supported | Automatic config migration and plugin bridge The bridge plugin and the ze exabgp command are compile-out-able with the ze_exabgp build tag (default-on in ZE_FEATURES); ze config migrate stays in every build (the migration library is always-on, only the runtime bridge gates). |