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. bgp update-delay max-delay <seconds> holds the first advertisement of a speaker that has just started until every configured peer has sent its End-of-RIB marker, until an optional establish-wait expires, or until max-delay expires, so a neighbor receives one settled route set instead of an initial set followed by corrections; RFC 4724 Section 4.1 prescribes the same deferral for a restarting speaker and requires the configurable upper bound. show bgp update-delay reports whether the speaker is holding and what it waits for. The whole BGP subsystem is compile-out-able with the ze_bgp build tag. The default feature list includes it, while 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 package main root, which splits into for the CLI registration under ze_core && ze_bgp and for the always-on seams under ze_bgp alone), 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 places always-on code used to call into the engine are inversion-of-control seams the engine fills from its own init: internal/component/config/infra carries four of them (ze config dump/diff/validate resolution, BGP peer validation, roleless-peer reporting, and the graceful-restart marker), and the MRT RIB dump provider is a fifth. le chaos run and le 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 with //go:build ze_bgp && ze_bmp, and a ze_bmp-without-ze_bgp build links neither BMP nor the engine. . cmd/ze/dispatch_bgp.gocmd/ze/infra_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.
Persistent storage Experimental One live database/ tree holds checksummed keys, credentials, runtime state, and per-config version pointers. Config history is content-addressed: each version's bytes are stored once under their SHA-256, and every read verifies the hash. One process owns writes; read-only inspection remains available. Explicit config files remain authoritative on every explicit-file start. Daemon commits update the file and history through a recoverable publication intent. ZeFS blobs remain explicit offline artifacts and appliance seeds.
Backup and restore Experimental ze data backup <file> [spare <n>] copies every key of the live store to one new 0600 blob artifact under the write lock, so a commit is in the backup whole or not at all. ze data restore <file> config [name <source-name>] commits one config from a verified artifact as a new version of this device's config and leaves credentials, identity and history unchanged. ze data restore <file> full replaces the whole tree, moves the previous tree to database.replaced-<stamp>, and records a durable intent, so an interrupted restore is refused by every opener until the same command finishes it. Both verbs are offline; while a daemon owns the store, request data backup path <file> and request data restore path <file> config run over SSH, and a restored config becomes active only when the reload accepts it. ze init --from <path-or-url> [--sha256 <hex>] creates a store from a blob, with the digest checked before a key is written. ze config edit --backup <artifact> edits a config inside a backup with no daemon.
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), each carrying an outer-header TTL of 64 when the config names none, so an encapsulated packet survives a multi-hop underlay; ttl 0 still asks the kernel to inherit the inner packet's TTL, and the default reaches netlink-backed tunnels only, because the VPP tunnel calls carry no TTL field, so a commit that names a ttl on a vpp-backed tunnel is refused by path rather than accepted and dropped , 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 one advertisement 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, BFD strict mode (draft-ietf-idr-bgp-bfd-strict-mode: capability 74 negotiated in OPEN, the BFD session opened before the BGP FSM starts, the BGP session held in OpenSent until BFD is Up, the BfdHoldTimer for a negotiated hold time of zero, and FSM events 30 to 35), 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 store exists. ze install systemd manages the systemd service: requires an existing database/ tree, creates the ze user/group, transfers store ownership explicitly, 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 a store 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 the store (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: The default feature set in feature-gates.txt is included in normal and appliance builds, 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 (le test, le chaos run, le perf, le mrt) 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) and are programmed directly, via netlink multipath or VPP via GoVPP. A MAIN-table route is a Loc-RIB path instead: rib { distance { static N } } ranks it against BGP, OSPF and IS-IS for the same prefix, and the FIB plugin programs the winner as RTPROT_ZE, so one writer owns the entry. Such a route needs a FIB plugin and Ze loads one: static declares that it needs a data plane, each FIB plugin declares the data plane it programs, and the engine resolves the pair against the backend interface { backend } selects, so no fib { } block is required and an explicit one always wins.
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. The prefix is also a path in the shared Loc-RIB, so rib { distance { connected N } } ranks it against BGP, OSPF, IS-IS and static for the same prefix, and it makes a next-hop covered only by an interface prefix resolvable. Ze still programs no kernel route for it: the protocol declares that the OS installs its entries, so a connected winner WITHDRAWS the route Ze had programmed rather than adding a second one beside the kernel's own.
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 binding by name or wildcard prefix (e.g., l2tp*). A policy that names several interfaces matches traffic on any of them. Ze installs one nftables rule per named interface. 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. Selection and programming are separate stages: the system RIB picks one winner for each prefix, then decides whether to program it. rib { fib-withhold } naming the winner's protocol is one of the reasons the write is declined, and the Per-protocol FIB withholding row states what withholding leaves intact. 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. A prefix and a PATH own different fields, and the split is at the type rather than at each call site: the label stack and the SRv6 SID belong to the forwarding path, so an equal-cost member promoted over an unreachable winner is programmed with its own, while the protocol, the route type, the metric and the fast-reroute alternate belong to the prefix and come from the winner whichever member takes it. Whether ze holds an install is the PRESENCE of the prefix in its installed map, never the validity of the address there, because a member named by a device alone resolves to an invalid address. The install also records the PROOF it was made under: a next-hop inside a connected covering route resolves to itself, so the address cannot say which rule programmed it, and a cascade that finds a once-proved path now unproved is the one case that withdraws. 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.
Per-protocol FIB withholding Experimental rib { fib-withhold [ bgp isis ]; } names the protocols whose selected routes ze does not write to the forwarding table. The list is empty by default, so every protocol is programmed. It accepts a name only when a protocol registered it, and a commit that names anything else is refused with the names ze accepts. A withheld route is not a dropped route. It stays in the Loc-RIB, and show rib and show ecmp-groups report it with the equal-cost paths that competed for the prefix, because both commands read the RIB rather than the last group written to the FIB. It still wins the prefix on administrative distance when its distance is the lower one. redistribute still moves it into another protocol, and the protocol's own event stream still carries it. Only system-rib/best-change, the stream every FIB writer reads, drops it, and the prefix leaves the next-hop table with it, so show nexthop-table stops listing it under its next-hop. A commit that changes the list sweeps the routes already programmed: ze withdraws each prefix it has programmed for a protocol the list now names, programs each prefix whose protocol the list no longer names, and rewrites the multipath group of a prefix that lost or gained a member. The sweep is what makes the change reach a converged router, which sends no further update for a prefix. Permitting a protocol programs its prefixes the way selection does, so a next-hop no route in the Loc-RIB covers is programmed all the same: such a gateway is on-link on an OSPF or IS-IS link whose connected route no plugin inserted, and reading it as unreachable would leave the prefix out of the forwarding table for good. An SRv6 Service SID that reaches nothing is the one refusal the sweep keeps. The setting covers every FIB writer. They all read one publish stream, so it cannot write BGP into VPP and withhold it from the kernel. A withheld protocol is no member of an equal-cost group either, so traffic does not reach its gateway through a multipath route a permitted protocol won. A prefix leaving the RIB is withdrawn from the FIB only where ze has an install outstanding for it, so a route ze never programmed produces no delete and no FIB sync error.
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: the default-on feature set includes it in normal and appliance builds, 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, in BOTH address families, as a P-clear Type 7 into a regular NSSA (the OSPFv3 NSSA-LSA 0x2007, whose P-bit rides in the prefix options) and a Type 3 at default-cost into a no-summary one (::/0 as an Inter-Area-Prefix-LSA for OSPFv3), which leaves default-originate meaning only an internal router's P-set Type 7 default, itself conditional on a usable non-zero forwarding address and inert in a no-summary NSSA per §1.3; a border router refuses a received Type 7 default whose P-bit is clear and every Type 7 default while it suppresses summary import, and an internal router installs both), 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: the default-on feature set includes it in normal and appliance builds, 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, priority and track are per group. accept-mode is enforced on the dataplane: an Active router that does not own the address and carries accept-mode false installs a host-scoped drop for each virtual address at the input hook, so the address does not answer traffic and does not answer a ping, which RFC 9568 Section 6.4.3 requires. IPv6 Neighbor Solicitation and Neighbor Advertisement are accepted ahead of every drop, per Section 6.1, so failover still resolves. track names interfaces whose failure subtracts a priority-decrement from the advertised priority, so an Active router that loses the uplink it forwards through hands the virtual address to a peer; decrements sum, the floor is 1, and the address owner still advertises 255. 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: the default-on feature set includes it in normal and appliance builds, 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, 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 Transcript recording of ze cli and ze config edit sessions to, written by the process that runs the session's model: the daemon for a session on a stored configuration, the client for ze cli -c. Preserves command input and output for post-disconnect recovery, with credential tokens on the command line replaced by <redacted>. 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 le 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 ./le repo generate. The browser library is htmx 4.0.0, 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: normal and appliance builds 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. Every response carries the security headers, and the X-Ze-Version build banner among them. environment { hide-version true; } takes that banner off the web interface and the Looking Glass together; the default sends it, and no other header changes. Ze sends no Server header at all.
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. The certificate leaf names a pki { certificate <name> } entry, and the listener then serves that leaf with every intermediate the store holds. An unset leaf keeps the self-signed certificate. An optional token gates every route and is compared in constant time. An unset token 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. Each command declares two help texts: a one-line summary every listing prints verbatim, and a long explanation only that command's own help page prints. A YANG command node declares them as ze:help and description. A plugin declares them as short-help and description in its Stage 1 registration. No surface derives one from the other. Inside ze cli, Tab reaches both halves. The completion menu lists command names, and the second message line above the prompt carries the summary of the selected command. Tab with nothing left to complete opens that command's long explanation in its own box. A command argument, an rpc or notification leaf, a config node and an enum value declare the same pair, and every surface that names the node renders both: the CLI JSON help, the site and wiki catalogs, the MCP tool schema, the web admin and config forms, the API and gRPC schemas, the published configuration reference and the completion menu. A config node several modules declare shows every declaration's summary, joined, because nothing in the schema says which module owns the node.
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-pki-ca-root-missing and doctor-pki-ca-root-expiry (the local certificate authority root absent, or within 90 days of expiry), 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). Kernel capability gate: a subsystem registers the kernel feature it needs, a predicate that reads the config tree, and a native probe (netlink or procfs, never an external binary). When the configuration uses the subsystem and the kernel lacks the feature, ze doctor reports it at error severity, ze REFUSES to start with the same message and exit 1, ze config validate fails, and a reload is refused with the running configuration still serving. A capability that cannot be DETERMINED warns and ze starts, so an unreadable probe never turns a working deployment into a dead one. There is no operator override. MPLS (CONFIG_MPLS_ROUTING, from the fib kernel plugin) and IPsec (CONFIG_XFRM_USER, from the ike engine) are enrolled. An empty vpn { ipsec { } } block installs no Security Association, so it gates nothing, warns about no kernel module and binds no IKE listener: one predicate answers for all three. Ze WRITES net.mpls.platform_labels before it programs its first label, because a label space of 0 is a dead configuration ze can repair rather than report. --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.
Don't Fragment probes Supported show ping, resolve ping, show traceroute and resolve traceroute take do-not-fragment honor-cache or do-not-fragment bypass-cache. The probe socket installs IP_MTU_DISCOVER and IP_RECVERR, the Linux kernel runs RFC 1191 and RFC 8201 path-MTU discovery, and the reply that a router refused carries status: too-big, next-hop-mtu-reported and next-hop-mtu read off the socket error queue, never parsed from ICMP by ze. A zero or an IPv6 value below 1280 is reported as no usable value. bypass-cache ignores the kernel's cached estimate so the wire answers. Without CAP_NET_RAW, ping runs on the unprivileged datagram ICMP socket when the daemon's group is inside net.ipv4.ping_group_range; ze doctor reports doctor-icmp-probe when neither socket opens and doctor-icmp-probe-unprivileged when only the datagram one does (traceroute needs the raw socket). Linux only.
Path MTU diagnostic for IPsec tunnels Supported show mtu measures the path MTU to every IPsec peer's installed endpoint and to a reference address with Don't Fragment ICMP probes (a reported MTU confirmed on the wire, then a candidate ladder and a bisection when ICMP is filtered), derives each tunnel's ESP ceiling from the transform it negotiated, and reports each bound xfrm interface as oversized, tight, under-utilized, ok, down or without a usable MTU, with the set interface ... mtu commands to apply and whether the access circuit or only the peer paths are clamped. A peer whose IKE SA is up is measured a second time over the SA itself, with an INFORMATIONAL request padded to the size under test: the authenticated reply confirms the ICMP figure or refutes it where ICMP errors are filtered, and the row names its prober (ike, icmp). show mtu host <address> measures one path; force bypasses the kernel's cached estimate. The command changes nothing. Linux only.
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: le 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.
Packet Capture and Decode Supported capture-raw start bgp records whole BGP messages in memory, and show capture-raw dump bgp pcap writes them as a pcap under LINKTYPE_RAW (101) with a synthetic IP and TCP header around each message, so Wireshark dissects the file as BGP. The 19-byte BGP header is rebuilt at the tap, sequence numbers advance per direction, checksums are computed, and IPv6 peers are written in their own family. A truncated message declares its on-wire length, so a reader shows truncation rather than a malformed packet. Reading back: ze bgp decode pcap <file> decodes every BGP message in a capture ze wrote or tcpdump wrote, reassembling each TCP direction first; ze bgp decode pcap - and ze bgp decode - take a capture and hex lines from standard input. A hole in the capture is reported and never read across, and a file with no BGP in it exits non-zero naming what it examined. The TCP ports are fabricated as 179 at both ends, because reading the real pair costs two mutexes on the session read path, so a ze-written capture is a readable reconstruction rather than a packet-level record.
Memory Lock Supported The memlock plugin locks the running ze executable's file-backed pages with mlock2(2) and MLOCK_ONFAULT, so the kernel cannot evict the daemon's own text under memory pressure and fault it back from disk. Linux only. The lock is taken in the package init(), before main(), so every ze process holds it from its first instruction. The whole mapped size is charged against RLIMIT_MEMLOCK, which the generated ze.service unit lifts with LimitMEMLOCK=infinity; a limit too small for the binary makes the lock fail, and show plugin list reports the memlock row as soft-failure carrying the cause and the remedy. The failure is soft: the daemon serves every session correctly with an unlocked executable. A ze doctor check answers the question the record cannot, before ze runs: it compares RLIMIT_MEMLOCK against the size of /proc/self/exe and warns under doctor-memlock-rlimit-low when the limit cannot hold the executable, staying silent for a process that holds CAP_IPC_LOCK.
Plugin Setup Results Supported Every registered plugin records, from its own init(), what its setup achieved: succeeded, soft-failure, or hard-failure, with the reason an operator acts on. show plugin list carries that record in its outcome and reason columns, in any ze process with no daemon running, and lists a plugin that recorded nothing as unknown rather than omitting it, so a feature that is absent for an environment reason can say why. The daemon reads the same registry once, at the first statement of hub.run, and refuses to start when a plugin recorded a hard failure, naming every failing plugin rather than the first. A CLI verb never reaches that gate, so the command that reports the fault keeps working on a host where the daemon will not boot. This is not show health, which runs a probe now: this replays an outcome decided once, before main().
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. Kernel panics are captured too: system crash-dump enabled true plus an appliance built with image.crash-dump reserves a named memory region the kernel writes its own panic message and backtrace into, which a warm reboot does not clear. Ze harvests the record on the next boot as a kernel-kind report in the same directory, under the same retention count, listed by the same show crashes. No Ze code runs at the moment of the fault. show crashes reports configured and armed separately, because a reservation is a boot argument: three ze doctor checks report the reservation, the record store and the crash directory. A full memory image is a separate opt-in and is amd64-only. Prometheus: ze_crashes_artifacts and ze_crashes_kernel_capture_armed, so a fleet can alert on capture being silently unarmed. . /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
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: normal and appliance builds 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. normal and appliance builds 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
Password Weakness Warning Supported Every password-set path judges the plaintext before it hashes it and warns when the password is shorter than 8 characters or matches one of eight common passwords (password, 123456, 12345678, qwerty, admin, letmein, root, changeme, compared whole and case-insensitively). The warning is advisory: the password is hashed and set, the commit succeeds, and the exit code stays 0, so a config Ze accepted before the check existed still commits. One policy serves ze config set, ze config deactivate, the CLI editor commit, the daemon load path, the REST/gRPC/gNMI commit path and ze passwd; the CLI surfaces name the leaf on stderr or in the commit status, and the daemon paths log at WARN. The reason names the rule and never the password.
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. normal and appliance builds 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 and ./le mrt. 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. Every certificate and key leaf takes a PEM document or base64 DER, 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), the looking glass (environment.looking-glass.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 is an error, and no listener falls back to the self-signed certificate. Health check (degraded at 30 days, down when expired). Report bus warnings for approaching expiry. show pki certificates, show pki certificate name <name>, and one command per export form: pem, bundle pem and fingerprint. Shared infrastructure for IPsec, TLS, and future mutual-auth features. The package has a second half config does not reach: a local certificate authority. Ze generates one root at its first start, keeps it in ZeFS, and issues its own components' certificates from it, so a peer validates an ISSUER rather than a copy of one leaf. The root lives ten years, an issued leaf 24 hours, and every certificate backdates NotBefore by five minutes for clock skew. A listener does not outlive its leaf: it answers each handshake through plugin.ServingLeaf, which reissues once two thirds of the current leaf's life is spent. Three surfaces consume the root today: the plugin transport (the engine hands it to every plugin child as ZE_PLUGIN_CA_PEM), the managed hub's listener, and ze appliance init, which issues the appliance's web leaf from an appliance root. The operator-facing web, looking-glass and DoT/DoH listeners are unchanged and still self-sign when no certificate is named. show pki local-ca pem exports the root certificate for an operator to paste into another node's pki { ca <name> { certificate } } block; the root private key has no accessor and reaches no command, no log and no config dump. File permissions are its only protection, and they belong to the blob rather than to the key: ZeFS applies no per-key mode, and the whole store is 0600 because atomicWrite creates it with os.CreateTemp.
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). Operator-authored Security Policy Database entries under vpn ipsec policy, carrying the BYPASS and DISCARD dispositions of RFC 4301 Section 4.4.1 with a selector, a direction and a total order. 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 and MD5-Challenge 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, EAP, wire codec, and command surface and rejects a vpn { ipsec {} } block. Two packages stay always-on: ike/dataplane (the XFRM programming seam OSPF's RFC 4552 authentication also uses) and ike/crypto (the IKEv2 transform registry show mtu reads to size the IPsec tunnels the kernel holds).
IPsec EAP Authentication Supported EAP-MSCHAPv2 (type 26), EAP-TLS (type 13) and EAP MD5-Challenge (type 4) authentication for road warrior VPN clients inside IKEv2 IKE_AUTH exchange. authentication { mode eap-md5 } selects type 4, and it is never a default. Its AUTH payloads come from SK_pi and SK_pr, because the method derives no MSK. Adopting it writes one warning quoting the RFC 7296 Section 2.16 sentence against a method that establishes no shared key. 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. As the EAP peer, Ze negotiates the method instead of failing: a Request for an authentication type it does not run draws an RFC 3748 Section 5.3.1 legacy Nak (type 3) naming the configured method, and a Notification Request (type 2) draws a Notification Response with its message in the daemon log. On EAP-TLS the peer sends an anonymous Network Access Identifier rather than its local-id, because RFC 9190 Section 2.1.8 forbids a permanent identifier in the cleartext Identity Response: the realm is kept and the username omitted (@realm), and an identity carrying no realm the RFC 7542 Section 2.2 grammar accepts becomes the fixed anonymous. On EAP-TLS 1.3 the authenticator answers a Certificate Status Request with the OCSP response configured on the certificate it presents (pki certificate <name> ocsp-response), which RFC 9190 Section 5.4 makes mandatory; the peer refuses a chain whose stapled status is missing, expired, about another certificate or not good once certificate-status-request is set, and it re-checks the authenticator's certificate against that certificate's own OCSP responder over https as soon as the Child SA gives it a network, closing the SA when the responder reports a revocation.
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. Transport mode works across an address-translating NAT on both roles: RFC 7296 Section 2.23.1's selector substitution replaces the addresses the peer named with the addresses this node observed, so the SA the kernel programs matches the packets it really sees. The SA records which side each translation is on, and show vpn ipsec sa reports it as behind-nat and peer-behind-nat beside 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, EAP method negotiation by Nak, 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/interop-ipsec/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). show vpn ipsec dataplane sa/policy/drift reads Linux XFRM state and compares expected Child SAs with the SAD. VPP and noop report unsupported enumeration. SA displays carry kernel byte and packet counters, with null for unavailable measurements. Web page at with SA table. Health reports degraded for drift or an unknown dataplane observation. Prometheus retains the belief gauges ze_ipsec_sa_count, ze_ipsec_tunnel_up, and ze_ipsec_tunnel_degraded. Kernel gauges ze_ipsec_dataplane_sa_count{if_id} and ze_ipsec_dataplane_drift{peer} remove their series when observation fails. Show commands reach the shared pipe layer; ze help command --json publishes each command's operators. Address operators require declared address fields. . /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 decorate the fields a command DECLARES to hold an IP address, and are refused by name over a command that declares none. 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; standalone input carries no declaration, so there they walk every field whose value parses as an address.
ASN to Regional Internet Registry Supported ze resolve rir <asn> and show resolve rir <asn> answer which registry holds an AS number, and that registry's whois host. Both read a delegation table the binary ships as embedded data, so the answer needs no network, no daemon and no file on disk. update resolve rir refreshes that table from the five registry delegation files into the managed store under meta/rir/delegation, and the stored copy answers whenever its generation date is later than the shipped seed's, so an upgrade carrying fresher data takes over on its own. The refresh is all or nothing: a registry that does not answer, a file the parser refuses, and a run that read no ASN record each store nothing and leave the previous table answering. An AS number in no delegated range and a table that cannot be read stay two distinct answers on every path. An appliance behind a mirror names where each file is read from, per registry, under system/rir: a registry with no block is read from the file it publishes, and a mirror is HTTPS or plain HTTP from the host itself. The stored table names the URLs that run read in its Source: lines. ./le data asn-delegation write rewrites the shipped table through the same fetch.
Pipe Output Limiting Supported | first N and | last N pipe operators bound output to the first or last N ROWS. They act on rows, so a command whose answer has none refuses them by name rather than truncating a document (client-side truncation otherwise). 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.
Plugin-Declared Pipe Aliases Supported A pipe alias names an operator chain, so show bgp | summary says what show bgp | display router-id local-as uptime peers-configured peers-established says. A plugin names one for its own commands in the pipes list of its Stage 1 registration, and the engine parses the expansion once, at registration. The alias leaves the registry when the plugin stops, so a stopped plugin can start again. The pipe layer selects and re-sequences and computes nothing, so a command that offers an alias MUST emit its aggregate fields beside its detail rows. show bgp rpki does this, and show bgp rpki | summary answers the same record as show bgp rpki summary. command help "<name>" lists the aliases a command answers to, with the chain each one stands for. A declared alias resolves over ze cli -c and over ssh, and NOT inside ze cli with no command argument, which expands the chain in the client process.
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: normal and appliance builds 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 entity's own pattern-of-life via a per-(entity,feature) EWMA baseline (internal/core/stats), scores self-deviation plus peer-group rarity (prefix cohorts), and correlates multiple weak feature deviations on one entity into a single incident (capped/discounted combine, not naive sum). An entity is a SOURCE (an anomalous sender), a DEST (a distributed sink or a probed host) or a PORT; a port is identified by proto and port rather than by an address, and is scored by self-deviation alone because it has no cohort. 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 (labeled dimension=source/dest/port since 2026-08-18; it was a bare gauge, so an existing query stops matching and must sum over the label). The detector's ring records confirmations only, so a separate anomaly-observe plugin subscribes to the same events and keeps the incident LIFECYCLE: a bounded ring (incident-ring-size) opened on AnomalyDetected and finalized with an end time on AnomalyCleared, or by a one-second stale sweep (stale-incident-timeout) when a source goes silent and the detector evicts it without a clear. show anomaly observe returns that list newest-first with finalized incidents included, which is where a finished incident's duration is readable. The detector, the lifecycle store and the shape responder are all 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.
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 sets the per-session accounting interval for a deployment that left acct-interval unset, because RFC 2869 Section 2.1 gives a locally configured value precedence over the Access-Accept, 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 (./le test 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), CHAP (CHAP-Password §5.3 with a 16-octet CHAP-Challenge §5.40, both generated per login from crypto/rand), or EAP with MD5-Challenge or MS-CHAPv2 carried in EAP-Message attributes (RFC 3579 §3.1), selected by the auth-method leaf and defaulting to pap; RFC 2865 §4.1 and RFC 3579 §3.3 Note 1 permit one credential type and never two, so exactly one is sent and the socket refuses a request carrying two, and §2.2 makes chap and eap-md5 usable only against a server holding the password in cleartext while eap-mschapv2 needs the NT hash. 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. For the two EAP methods ze is the EAP PEER itself, answering with the password the operator typed rather than relaying frames, so no login transport changes: it splits one EAP packet across consecutive EAP-Message attributes at 253 octets, signs every EAP-bearing Access-Request with the RFC 3579 §3.2 HMAC-MD5, silently DISCARDS a reply whose Message-Authenticator does not verify so a forged packet cannot stop the AAA chain, and answers each Access-Challenge with the State attribute copied byte for byte and never read (RFC 2865 §5.24). The conversation is bounded by the peer's 20-round cap and the login time budget, neither of which a server can raise. EAP-TLS is implemented in the peer and not offered here: it needs an operator certificate and key. Admin-session accounting is 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 (./le test 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/interop-pppoe/
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). An IRR answer carrying no prefixes never replaces cached ones: the store keeps the last known good list, show firewall irr reports the entry as stale with the age of the data, ze doctor reports it, and clear firewall irr asn|as-set is the only path that removes prefixes. A binding with no prefixes produces no table at all, so an empty answer cannot leave an interface dropping everything. DNS-sourced address groups: a domain-group holds domain-names, and source-domain-group / destination-domain-group leaves in the from-block match the addresses those names resolve to (firewall-domain plugin). Each name is re-resolved on its own answer TTL, per family, clamped by ttl-floor, and the set is reprogrammed only when the addresses actually changed. Only NXDOMAIN empties a set: a SERVFAIL, a REFUSED or an unreachable server keeps the last good addresses, so a DNS outage is never enforced as a filter. Resolved addresses persist in ZeFS, so a restart with DNS down resumes filtering from them, and every change is appended to a bounded JSON-lines log at <config-dir>/firewall-domain-group.dns.jsonl. show firewall ruleset shows the DNS name beside each address through the show-enricher registry, adding no field to the shared set model. 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). The over-limit policy rides the rate-limit term as an nftables inverted limiter. The input chain keeps an accept policy under both settings, so no CoPP config locks the operator out. 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: send bgp <sel> blackhole and send bgp <sel> 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, and how much of the path the key holds follows what the SOURCE framed. A source that negotiated no ADD-PATH names a path by its prefix alone, so ze holds one identifier for that source's whole session and releases it when the peer is removed. A source that negotiated ADD-PATH names a path by (prefix, identifier), so ze holds one entry per family, received identifier and prefix, and frees it once it has relayed that pair's withdraw. Either way 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-04) Supported Receiver-requested ADD-PATH limit per family and prefix. Ze enforces the peer's negotiated limit across batches on each destination connection, including route-server fast-path forwarding. First admitted paths keep their slots. Replacements remain permitted, withdrawals free slots, and a new connection resets the count. Local capability { add-path { limit N; } } requests a receive limit, with per-family overrides. It does not guarantee peer compliance. See ADD-PATH and PATHS-LIMIT.
RPKI ASPA Policy Enforcement Supported ASPA path verification (draft-ietf-sidrops-aspa-verification-28) with configurable policy enforcement: rpki/aspa/action/invalid supports reject, log-only, accept. ASPA records use the RTR v2 format from draft-ietf-sidrops-8210bis-27.
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. docker build -t ze:latest -f docker/Dockerfile. builds the deployment image: a static binary on a scratch base, 119 MB, ENTRYPOINT /ze. docker build -t netlab/ze:latest -f docker/Dockerfile.lab. builds the lab image: tini and iproute2, 137 MB, because lab tools run sh and ip inside a node. Both Dockerfiles derive their build tags from feature-gates.txt. ZE_TAGS adds tags to that set. Compose support is in. . 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. ./le test 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).
Published RFC conformance ledger Supported Every requirement this repository extracted from an RFC is published, one page per summary under /quality/rfc-compliance/<stem>/, 190 pages today. Each page carries the requirement text, its level and section, the tests bound to it and the carrier that runs them, the audit verdict a reader recorded and how current it is, the recorded break each tagged unit was seen to fail under, and the extraction sign-off that decided which sentences became requirements. The disclosure is full: a gated MUST with no test, a weak, wrong or unimplemented verdict, a stale or shifted verdict, a tagged unit with no discrimination record, and a no-break record are each named under their own requirement id rather than folded into a count.