RFC Implementation Status
This page is Ze's public standards status page. It lists RFCs that Ze implements, partially implements, has deferred, or has explicitly decided not to implement. It is a product support view rather than a formal IETF compliance certificate: Supported means the behavior is implemented and tied to current source anchors; Experimental means implemented but still needs deployment evidence or hardening before production claims; Partial means a named subset is missing, intentionally skipped, or not proven; Unsupported means Ze explicitly does not implement it; Future means tracked but not shipped.
Reference summaries under rfc/short/ are not implementation claims by themselves. A row belongs here only when current docs, source code, tests, or learned closure notes tie the RFC to a Ze feature.
How strong the proof behind a row is. Every requirement's evidence is listed in that RFC's own file under rfc/requirements/, with a kind/tier cell per test link. kind is the layer the test exercises (unit, functional, editor, interop); tier is whether anything executes it. verify means it runs in make ze-precommit-verify on every push. interop/nightly runs only in the scheduled, advisory evidence-nightly workflow -- so a requirement whose evidence is entirely nightly-tier is marked **nightly-only** on its row there, and counted in its own column of the coverage rollup in ai/RFC-REQUIREMENTS.md, and a row here backed only by such evidence is not proven on the merge path. A tag in a suite no pipeline runs is refused outright rather than published with a caveat.
What on this page a machine checks, and what stays a human judgement. make ze-rfc-check enforces exactly four properties of the table below.
- Row presence for a new enrolment. An RFC added to
rfc/enrolled.txtmust arrive with a row here. A row that exists must not be deleted while its RFC stays enrolled. - Gap-count agreement. A Remaining cell can spell a number immediately before MUST or SHALL. That number must equal the count of
{gap}annotations in the RFC's summary. - Disclosure under a
{gap}. A summary can privately admit an unmet MUST. Its row here must then say so, in the Status cell or in the Remaining cell. - Enrolled or declared. Every summary under
rfc/short/is enrolled, or it carries a recorded reason inrfc/not-enrolled.txt. An un-enrolled RFC is therefore a decision rather than an absence.
Everything else in the table is editorial and no gate reads it. Status is a product-support judgement. It is neither the RFC's IETF category nor a derived value. Area is a hand-written label. Implemented coverage is source-anchored prose.
Two coverage limits are worth stating outright. A Remaining cell that spells its count in digits is not judged, and neither is one that puts words between the number and MUST. That is deliberate: the page uses a second convention where a number NEAR the word MUST is the not-applicable count. Separately, a row naming an RFC with no rfc/short/ summary sits outside the empty-checklist guard.
BGP base protocol, capabilities, and session safety
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 4271 | BGP-4 base protocol | ∿ | FSM, OPEN/UPDATE/NOTIFICATION/KEEPALIVE encode and decode, message-header and hold-time validation, well-known attribute recognition and flag rules, connection collision resolution, per-peer FSM and hold timer, the complete Section 8.2.2 Event 10 action list on a hold-timer expiry, which is the Hold Timer Expired NOTIFICATION sent before the connection is dropped (RFC4271-8.2.2-1), the ConnectRetryTimer zeroed (RFC4271-8.2.2-2), the BGP resources released (RFC4271-8.2.2-3), the TCP connection dropped (RFC4271-8.2.2-4) and the state changed to Idle (RFC4271-8.2.2-5), all on the FIRST expiry with no reprieve; the Section 8.2.2 ManualStop (Event 2) action list, which is the Cease NOTIFICATION sent before the connection is dropped, with RFC 4486 subcode 2 Administrative Shutdown, on every peer an administrative stop of the daemon ends a connection with, from OpenSent and OpenConfirm as well as Established (RFC4271-8.2.2-18, internal/component/bgp/reactor/reactor.go Stop); prefix-limit Cease, TCP MD5, the Adj-RIB-In, the RFC 4271 Section 9.1.2.2 decision process and the Loc-RIB install; the Section 5.1.4 propagation rule, which keeps a MULTI_EXIT_DISC received from one neighboring AS off every session toward another (RFC4271-5.1.4-1, applyFactsMED, internal/component/bgp/reactor/forward_med.go) while a metric ze or an egress filter originates still reaches the peer, and while an RS client keeps the value RFC 7947 Section 2.2.3 exempts; the Section 5.1.4 configured removal, which is the mechanism a speaker MUST implement (RFC4271-5.1.4-4): the del { med; } directive of a modify policy, on a policy attached to a peer's IMPORT chain drops MULTI_EXIT_DISC from the route, and it drops it before Decision Process phases 1 and 2 as RFC4271-5.1.4-2 requires, because the import chain's rewritten payload replaces the WireUpdate before the UPDATE is dispatched (ExtractMEDRemoveOps, internal/component/bgp/reactor/filter_delta.go; appendMEDRemove, internal/component/bgp/plugins/filter_modify/filter_modify.go, which refuses the directive on an export chain); the Section 5 and Section 9 pass-along rule for an attribute ze does not recognize, which sets the Partial bit to 1 on an unrecognized transitive optional attribute at receipt, on the bytes ze retains and relays (RFC4271-5-3, publishBase, internal/component/bgp/reactor/session_validation.go, and SetPartialOnUnrecognizedTransitive, internal/core/bgp/attribute/partial.go), while a well-known attribute, an optional non-transitive one and an attribute ze does recognize keep the bit the sender chose, and a bit an earlier AS set is never cleared; the Section 5.1.3 NEXT_HOP loop rules, which are the two halves of one hazard: on egress a route is withheld from the peer whose OWN address the NEXT_HOP names, whether ze RELAYS that route or ORIGINATES it (RFC4271-5.1.3-1). A relayed route is answered on both forward rails by egressNextHopIsPeerOwn (internal/component/bgp/reactor/forward_next_hop.go), where the address arrives as the third-party next hop Section 5.1.3 case 2 permits. An originated route is answered by originatedNextHopIsPeerOwn in the same file, asked at the two writers that put such a route on the wire, writeUpdateGated and SendAnnounce (internal/component/bgp/reactor/session_write.go), rather than at each of the five rails that produce one: configured static routes and default-originate, the RIB op-queue drain, the announce batch and the RFC 9494 stale re-advertise. On both sides the withdrawals travelling in the same UPDATE still reach that peer, a third-party next hop naming anyone else is still advertised, and the route is WITHHELD rather than rewritten, because the section states a prohibition on advertising and a rewrite would invent a next hop the operator never configured; and on install a route naming one of ze's OWN session addresses is excluded from the decision process rather than installed (RFC4271-5.1.3-2, gatherCandidatesLocked, internal/component/bgp/plugins/rib/rib_commands.go), so a sound alternative path to the same prefix still wins; requirements bound per line in. rfc/short/rfc4271.md |
Sixteen MUST/SHALL-level gaps, each annotated in. Header error reporting: RFC4271-6.1-1, 6.1-2 and 6.1-3 (a bad marker or a sub-19 length is detected but no NOTIFICATION is sent, internal/component/bgp/message/header.go and internal/component/bgp/reactor/session_read.go; the per-type minima and the ceiling do send a conformant Bad Message Length, session_read.go) and 6.1-4 (an unknown message type is reported with subcode 0 and a text string, not Bad Message Type with the erroneous Type octet, internal/component/bgp/reactor/session_handlers.go). OPEN error reporting: RFC4271-6.2-3 (an OPEN body that fails to decode returns from handleOpen with no NOTIFICATION and without closing the connection, internal/component/bgp/reactor/session_handlers.go; every other OPEN error rail does send Error Code 2). Attribute handling: RFC4271-5-5 (an unrecognized non-transitive optional attribute is retained and re-advertised rather than quietly ignored, internal/component/bgp/plugins/rib/storage/attrparse.go,254-260). Next-hop resolvability: RFC4271-3.1-2, 9.1.2-1, 9.1.2-4 and 9.1.2.1-2 (the decision process neither excludes an unresolvable NEXT_HOP nor re-runs on an IGP-cost change, and the Loc-RIB is not purged of unresolvable routes, internal/component/bgp/plugins/rib/rib_commands.go). Adj-RIB-Out: RFC4271-9.2-2 (no forwardability gate) and 9.2-3 (a route excluded by an egress filter is skipped without withdrawing the previous advertisement, internal/component/bgp/reactor/forward_rs.go). Timers: RFC4271-9.2.1.1-2 and 9.2.1.1-3 (no MinRouteAdvertisementIntervalTimer, internal/component/bgp/fsm/timer.go). Connections: RFC4271-8.2.1-3 (an inbound connection reuses the peer's session rather than getting its own FSM, internal/component/bgp/reactor/reactor_connection.go). Seven further requirements are recorded {not-applicable}: ze performs no route aggregation and never disaggregates a received route. rfc/short/rfc4271.md |
| RFC 6286 | AS-wide unique BGP Identifier | Supported | All three protocol revisions. Section 2.1: the local identifier is rejected when zero, on the global leaf and on a per-peer override (parseRouterID, internal/component/bgp/reactor/config.go), and AS-wide uniqueness is enforced by default through a claim taken during OPEN validation and released on session teardown (internal/component/bgp/reactor/routerid_unique.go), with bgp/session/allow-shared-router-id as the operator opt-out the lowercase "should" permits. Section 2.2: a received OPEN whose BGP Identifier is zero, or equals ze's own identifier and comes from an internal peer, is answered with OPEN Message Error / Bad BGP Identifier on BOTH receive rails -- normal receive and the connection that won collision resolution (internal/component/bgp/reactor/session_open_validation.go, called from session_handlers.go and session_connection.go); the same identifier from an external peer is accepted, as Section 2.2 requires. The LOSING connection of a collision is closed before Section 2.2 runs, so it receives Cease / Connection Collision (6/7) rather than Bad BGP Identifier even when its identifier is zero (rejectConnectionCollision, internal/component/bgp/reactor/reactor_connection.go). The connection closes either way and RFC 4271 Section 6.8 does not fix the ordering, but the subcode differs and this row does not claim otherwise. Section 2.3: identical identifiers in a connection collision preserve the connection initiated by the larger AS number (internal/component/bgp/reactor/session.go DetectCollision). Requirements bound per line in. rfc/short/rfc6286.md |
✕ |
| RFC 9072 | Extended Optional Parameters Length for BGP OPEN | ∿ | Extended OPEN encoding when Optional Parameters exceed 255 octets (Non-Ext OP Len/Type 0xFF markers plus 2-octet Extended Opt. Parm. Length), extended-form decode, and classic-form encode/decode; tests bound per requirement in. rfc/requirements/rfc9072.md |
Four MUST-level gaps, each annotated in: RFC9072-2-5, RFC9072-2-6 and RFC9072-3-1 -- the decoder (internal/component/bgp/message/open.go) selects the extended form only when Non-Ext OP Len equals 255, so it does not ignore that octet, does not consult the following octet for other non-zero lengths, and mis-parses a first type code of 255 with Non-Ext OP Len not 255 as a classic OPEN; and RFC9072-3-3 -- an unrecognized OPEN optional-parameter type is silently skipped (internal/core/bgp/capability/capability.go) instead of triggering the RFC 4271 Section 6.2 Unsupported Optional Parameter NOTIFICATION. rfc/short/rfc9072.md |
| RFC 5492 | BGP capability advertisement | Supported | Capability TLV parser, encoder, unknown-capability ignore behavior, negotiated session view. | ✕ |
| RFC 4760 | Multiprotocol BGP | Supported | AFI/SAFI capability negotiation, MP_REACH_NLRI, MP_UNREACH_NLRI, family-specific UPDATE handling. | RFC 7606 MP attribute ordering tradeoff is tracked under RFC 7606. |
| RFC 6793 | 4-byte AS numbers | ∿ | ASN4 capability advertisement and negotiation, 4-octet AS_PATH/AGGREGATOR between NEW speakers, 2-octet AS_PATH with AS_TRANS toward OLD speakers, AS4_PATH and AS4_AGGREGATOR construction (confederation segments excluded), AS4_PATH/AS4_AGGREGATOR malformed-attribute validation, AS_TRANS in the OPEN My AS field; tests bound per requirement in. rfc/requirements/rfc6793.md |
Twelve MUST/SHALL-level gaps, each annotated in. Peer identity: RFC6793-4.1-3 -- UnpackOpen never populates Open.ASN4 from the code-65 capability (internal/component/bgp/message/open.go), so the session's OPEN-derived peer AS falls back to the two-octet My AS field (internal/component/bgp/reactor/reactor_dynamic.go). Receive-side reconstruction (RFC 6793 Section 4.2.3): RFC6793-4.2.3-3/-4/-5/-6/-7 -- nothing compares the AGGREGATOR AS against AS_TRANS to choose between AGGREGATOR and AS4_AGGREGATOR (internal/component/bgp/plugins/rib/storage/attrparse.go,:138-140); RFC6793-4.2.3-8/-9/-10 -- canonicalizeASPath substitutes a received AS4_PATH wholesale instead of running the AS-number-count comparison and leading-segment prepend (internal/component/bgp/plugins/rib/storage/attrparse.go), and MergeAS4Path, which implements that algorithm, has no non-test caller. Error handling: RFC6793-4.1-6 and RFC6793-4.1-7 -- an AS4_PATH or AS4_AGGREGATOR received from a NEW speaker is used and forwarded rather than discarded, so the attribute is carried in an UPDATE between NEW speakers (the NEW-to-NEW fast path copies the attribute section verbatim, internal/component/bgp/wireu/aspath_rewrite.go,:341,:363, and the full rewrite copies it through at internal/component/bgp/wireu/aspath_rewrite.go,:521-524); ze itself originates neither attribute toward a NEW peer; RFC6793-6-5 -- a malformed AS4_AGGREGATOR is carried through unchecked (no RFC 7606 validator for type code 18). rfc/short/rfc6793.md |
| RFC 2918 | Route Refresh | Supported | Route Refresh capability and ROUTE-REFRESH message handling. | ✕ |
| RFC 7313 | Enhanced Route Refresh | Supported | BoRR and EoRR support, capability checks, bounded route resend. | Four MUST-level receive-side gaps annotated in: RFC7313-4-4/4-5 -- a received BoRR/EoRR is log-only (internal/component/bgp/plugins/rib/rib.go), so ze marks no Adj-RIB-In routes stale and purges none; and RFC7313-4-6/4-7 -- neither the send nor receive path applies a Graceful-Restart End-of-RIB gate to BoRR emission or acceptance. rfc/short/rfc7313.md |
| RFC 7911 | ADD-PATH | Supported | Per-family send and receive modes, Path ID packing, NLRI path IDs where negotiated; a re-advertised route carries ze's own Path Identifier (RFC7911-2-2), assigned per ingress path in and read by both the raw same-context forward and the re-encode, so an announcement and its withdraw leave under one value and two clients that chose one identifier for a prefix stay two paths at a third; tests bound per requirement in. internal/component/bgp/reactor/forward_path_id.gorfc/requirements/rfc7911.md |
Closed 2026-08-14: RFC7911-2-2. Until then ze relayed the ingress Path Identifier, so a route server merged two clients' paths for one prefix into one and lost a route. |
| RFC 8654 | BGP Extended Message | Supported | Extended message capability and 65535-byte message limit when negotiated. | ✕ |
| RFC 8950 | Extended Next Hop | Supported | IPv6 next-hop for IPv4 NLRI and negotiated extended next-hop lookup. | ✕ |
| RFC 5549 | Legacy extended next-hop encoding | Supported | Backward-compatible parser for the older format superseded by RFC 8950. | Main public claim uses RFC 8950. |
| RFC 4724 | Graceful Restart | ∿ | Receiving-Speaker Graceful Restart: GR capability advertise and last-instance negotiate, Restart-State/Forwarding-State bit encoding, End-of-RIB send and detect, mark-stale on a non-NOTIFICATION drop, stale deletion on consecutive restart / Restart-Time expiry / F-bit-clear, and GR-stale routes competing normally in best-path (internal/component/bgp/plugins/gr, internal/component/bgp/plugins/rib). Requirements bound per line in rfc/short/rfc4724.md. | Eight MUST gaps annotated in rfc/short/rfc4724.md: ze implements the Restarting-Speaker path as GR signaling only and does not retain its own Loc-RIB across a restart, so it runs no selection-deferral cycle and exposes no Selection_Deferral_Timer (RFC4724-4.1-1, 4.1-2, 4.1-3, 4.1-5, 4.1-6, 4.1-8); and on a re-established GR-capable session it follows plain RFC 4271 Section 6.8 collision detection (closes the new connection, keeps the existing session) rather than the RFC 4724 Section 4.2 override that treats the new OPEN as terminating the old session (RFC4724-4.2-1, 4.2-2). |
| RFC 9494 | Long-Lived Graceful Restart | ∿ | Helper-side LLGR: capability code 71 declared only alongside GR and disregarded when GR is absent, per-family Long-Lived Stale Time decode and advertisement, GR-to-LLGR handover with per-family LLST timers, NO_LLGR deletion and LLGR_STALE attachment on LLGR entry, LLGR_STALE routes treated as least preferred in best-path, and partial deployment toward non-LLGR neighbors (NO_EXPORT plus LOCAL_PREF=0 for iBGP, withdrawal for eBGP) (internal/component/bgp/plugins/gr, internal/component/bgp/plugins/rib). Requirements bound per line in rfc/short/rfc9494.md. | Five MUST gaps annotated in rfc/short/rfc9494.md: LLGR_STALE is not protected on further advertisement -- a peer whose send-community omits standard (or is none) has the whole COMMUNITIES attribute suppressed on the readvertise rails (internal/component/bgp/reactor/peer_forward_facts.go, reactor_api_forward.go, forward_rs.go), and a community-remove ffff0006 filter strips it through removeValues, which exempts no value (internal/component/bgp/plugins/filter_community/handler.go) (RFC9494-4.3-2); on a consecutive session drop ze applies the RFC 4724 purge of previously stale routes and re-arms a full LLST timer, so LLGR-marked routes are deleted early and the running timer is reset (RFC9494-4.2-7, 4.2-9); LLST timers are stopped at re-establishment, so an LLST elapsing during synchronization is not recorded and a subsequent reset removes nothing immediately (RFC9494-4.2-10); and long-lived-stale-time is configured per peer and applied to every negotiated family rather than per AFI/SAFI (RFC9494-5-2). |
| RFC 9234 | BGP Role and OTC | Supported | Role capability negotiation, role mismatch NOTIFICATION, OTC egress stamping, OTC ingress leak detection and treat-as-withdraw, unicast-only (AFI 1/2, SAFI 1) OTC scoping read from MP_REACH_NLRI or, for a withdrawal, MP_UNREACH_NLRI. Both stamping rules are conditioned on the UPDATE advertising reachable NLRI, per Section 5's "if a route is to be advertised" / "if a route is received", so a withdrawal, an MP_UNREACH-only UPDATE and an End-of-RIB marker (both RFC 4724 encodings, which an added attribute would stop being a marker at all) are never stamped (payloadAdvertisesNLRI, isPayloadUnicast). internal/component/bgp/plugins/role/otc.go |
✕ |
| RFC 8516 | FQDN capability | Supported | Hostname capability code 73, decode support, per-peer hostname and domain advertisement. | Some older plugin comments still reference the draft name. |
| RFC 2545 | IPv6 next-hop handling | Supported | Enrolled 2026-08-07. All four SHALL-level requirements sit in Section 3 and govern the MP_REACH_NLRI Network Address of Next Hop field; each is proven in both polarities by verify-tier functional tests, bound per line in and listed in. Send side: ze evaluates the Section 3 condition itself. linkScope.linkLocalNextHop emits the 32-octet form (global address first, link-local second, length octet 0x20) only when the speaker shares a locally connected subnet with the peer AND with the entity named by the global next hop, and the 16-octet form (one address, length octet 0x10) in every other case. Connected subnets come from network.ConnectedPrefixes, snapshotted per session. The session > link-local leaf supplies the address; it does not decide inclusion. The snapshot is re-settled for every established peer when an address is added to or removed from ANY interface, refreshPeerLinkScopes, so a change on an interface other than the session's does not leave a stale answer behind a surviving TCP session. A link-local address is refused wherever it could reach the FIRST slot of that field: the three route-level entry points (ParseRouteAttributes, handleAnnounceUnicast, parseNhopFlat) and the two peer config leaves connection > local > ip and session > next-hop (ValidatePeerGlobalNextHop) all call ValidateGlobalNextHop, and linkScope.linkLocalNextHop refuses independently of all of them, appending nothing when the global address it is given is not a global IPv6 address. Receive side: parseNextHops accepts lengths 16 and 32 for an IPv6 next hop and rejects every other length for AFI 2. rfc/short/rfc2545.mdrfc/requirements/rfc2545.mdinternal/component/bgp/reactor/link_scope.gointernal/core/network/connected.gointernal/component/bgp/reactor/reactor_iface.gointernal/component/bgp/reactor/config_nexthop_form.gointernal/core/bgp/attribute/nexthop_form.gointernal/core/bgp/attribute/mpnlri.go |
✕ |
| RFC 4486 | BGP Cease subcodes and prefix maximum | Supported | The subcode catalog for error code 6, subcodes 1 to 8. Also max-prefix teardown, retry backoff, and the operator reset paths. Enrolled 2026-07-30. Its sole MUST-level requirement is proven in both polarities. That requirement binds subcode 1 to the teardown a prefix-maximum breach causes. asserts the bytes on the wire, including the optional AFI/SAFI/upper-bound Data field of Figure 1. The ten advisory statements of section 4 are bound per line in. internal/component/bgp/message/notification.gotest/plugin/prefix-maximum-enforce.cirfc/short/rfc4486.md |
✕ |
| RFC 6608 | BGP FSM Error subcodes | ∿ | The RFC 6608 FSM Error subcodes (1 OpenSent, 2 OpenConfirm, 3 Established) are defined and decoded for display of a received NOTIFICATION. | Three MUST gaps, gated in: ze does not originate a code-5 FSM Error NOTIFICATION with these subcodes on an unexpected message; the reactor dispatches received messages by type without an FSM-state guard. rfc/short/rfc6608.md |
| RFC 7607 | Prefix-limit enforcement context | Supported | Prefix-limit enforcement is called before route admission and documented with RFC 4486. | ✕ |
| RFC 9687 | Send Hold Timer | Supported | Send Hold Timer, auto duration max(8min, 2x hold-time), NOTIFICATION code 8 on expiry. |
✕ |
| RFC 7606 | Revised UPDATE error handling | ∿ | Structural UPDATE validation, treat-as-withdraw (routes are synthesized into withdrawals and removed from the Adj-RIB-In), attribute-discard decisions, session-reset with NOTIFICATION UPDATE Message Error, per-attribute validation for 15 attribute codes, inner MP_REACH/MP_UNREACH NLRI overrun and RFC 4760 flag-consistency validation (§5.3, session reset via §3.j) for IPv4/IPv6 unicast and multicast (ADD-PATH aware, RFC 7911 path-ids skipped when negotiated), tests bound per requirement in. rfc/requirements/rfc7606.md |
One MUST-level gap, annotated in and gated by make ze-rfc-check: §5.1 first bullet: Ze intentionally emits MP_UNREACH first and MP_REACH last (docs/architecture/wire/mp-nlri-ordering.md). Closed 2026-08-01: §5.4. A route whose NLRI type Ze does not implement is now discarded at ingress, in enforceRFC7606, so it reaches neither the RIB nor the forward path. The RIB alone would not have sufficed, because reactorForwardRS relays the received wire without consulting it. The ruling is per family, because §5.4 discards "unless the relevant specification for that address family specifies otherwise" and RFC 9552 §5.2 uses that clause to REQUIRE preserve-and-propagate for BGP-LS. Each NLRI plugin registers its own recognizer (internal/core/bgp/nlri/nlritype), so the three families §5.4 binds each carry their own ruling: EVPN discards route types outside 1. 5, MCAST-VPN outside 1. 7 (RFC 6514 §4), and BGP-MUP anything but Architecture Type 1 with Route Type 1. 4 (draft-ietf-bess-mup-safi §3.1). BGP-LS registers no recognizer and propagates unchanged, and a family nobody has ruled on keeps its previous behavior. Judging a route type needs the section carved into single NLRIs, so MCAST-VPN and BGP-MUP gained nlrisplit splitters, which also made nlrisplit.Supported true for them: ze now installs both as opaque Adj-RIB-In entries the way it already installed EVPN (insertPoolNLRIs). This reverses the divergence disclosed here until 2026-08-01. Closed 2026-07-20: §7.13, §7.15 and §7.16 gained attribute validators for codes 24, 25 and 128, §7.15's unrecognized-type tolerance is now met by design rather than by omission (the validator reads length only), and §6's debugging facility now logs the NLRI involved and the entire malformed UPDATE. Also closed 2026-07-20: §5.1's second bullet. Ze already originated only compliant UPDATEs; the relay now splits a received mixed shape as well, in both the zero-copy same-context branch and the re-encode branch, so every UPDATE ze sends carries at most one NLRI-bearing field. A compliant single-field UPDATE keeps the zero-copy forward: the shape verdict is cached per received message and the received bytes are handed on unchanged. Receive-side tolerance of any position or combination (§5.1 third bullet) is unchanged. rfc/short/rfc7606.mdinternal/component/bgp/reactor/session_validation.gointernal/component/bgp/plugins/nlri/evpn/rfc7606.go.../nlri/mvpn/rfc7606.go.../nlri/mup/rfc7606.gointernal/component/bgp/plugins/rib/rib.go |
| RFC 8203 | Administrative Shutdown Communication | Supported | UTF-8 shutdown message for Cease/Admin Shutdown and Cease/Admin Reset. | Sender keeps the conservative 128-byte RFC 8203 limit. |
| RFC 7947 | BGP Route Server | Supported | Transparent RS forwarding for RS clients by default: no AS_PATH prepend, no NEXT_HOP rewrite, MED preserved (verbatim wire forwarding); per-client import/export policy applied on redistribution. Sections 2.2.2.1 and 2.2.3 state the AS_PATH and MULTI_EXIT_DISC rules as recommendations, so an operator policy may override each. | Optional per-peer next-hop override and path-hiding mitigation (add-path) are operator-configured. A modify policy with as-path-prepend or del { med; } also changes what an RS client receives. |
| RFC 9003 | Administrative Shutdown Communication update | Supported | Receiver accepts the updated UTF-8 format and 255-byte length field; invalid UTF-8 is stripped on send. | Sender remains conservative at 128 bytes for interoperability. |
| RFC 8538 | Notification GR | Unsupported | Cease Hard Reset subcode (9) name is known. | The GR N-bit is decoded but not acted upon; routes are dropped on any NOTIFICATION, so RFC 8538 route retention is not implemented. |
| RFC 5065 | BGP confederations | Unsupported | None claimed. | Explicitly unsupported. |
| RFC 2385 | TCP MD5 | Supported on supported kernels | Per-peer MD5 password plumbing and OS-specific socket hooks. | Platform availability depends on kernel support. |
| RFC 5082 | GTSM and TTL security | Supported on Linux | TTL security settings and BFD/BGP TTL gates where supported. | Platform availability depends on kernel support. |
| RFC 5925 | TCP-AO | Unsupported | None claimed. | Not implemented. |
BGP attributes, families, policy, and NLRI
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 1997 | Standard communities | ✓ | COMMUNITY attribute parsing (multiple-of-4 length enforced), encoding, JSON, well-known names, operator community-match policy, and the well-known egress operations. A route received carrying NO_EXPORT or NO_EXPORT_SUBCONFED is withheld from every external peer, and one carrying NO_ADVERTISE from every peer. The suppression is automatic and needs no operator policy; it is counted by ze_bgp_wellknown_community_suppressed_total. Ze configures no confederation, so the confederation boundary is the AS boundary, which is what RFC 1997 says to do for "a stand-alone autonomous system that is not part of a confederation". |
Gated per requirement in make ze-rfc-check. rfc/short/rfc1997.md |
| RFC 3765 | NOPEER community | Supported | The NOPEER well-known community (0xFFFFFF04): parsing, text output and display. Enrolled 2026-07-30 on an evidenced zero rather than on a MUST. RFC 3765 is Informational and invokes RFC 2119 nowhere. It describes its own mechanism as "an advisory qualification to readvertisement of a route prefix". Both checklist rows in [MAY], and the document gates nothing. The section-by-section walk behind that claim is recorded in. rfc/short/rfc3765.mdrfc/extraction/rfc3765.json |
✕ |
| RFC 7999 | BLACKHOLE community | ∿ | The BLACKHOLE community (65535:666) is recognized on receive, and Ze honors it per BGP session. Section 3.3 states two conditions in one MUST sentence, and Ze enforces both. The peer must have agreed on that session by naming the community it honors (blackhole communities), which accepts the well-known value under either spelling and an operator's own value, because operators run RTBH on their own community far more often than on 65535:666. The announced prefix must be covered by an equal or shorter prefix that peer is authorized to advertise (blackhole prefixes). Coverage is not prefix-list membership: a /24 authorization covers the /32 inside it (coveredByAuthorized, internal/component/bgp/plugins/rib/rib_blackhole.go). A honored route becomes a discard route in the Linux FIB (RTN_BLACKHOLE) and a drop path in VPP. Section 4's default holds: a blackhole container with neither leaf-list, and a peer with no container, discard nothing. Stating prefixes alone is itself the explicit configuration directive Section 4 asks for, so it resolves the community to the well-known value; a stated communities list is taken exactly and the well-known value is never added to it. Section 3.1's send-side obligation is met on the origination path. announce blackhole, and announce unicast community 65535:666, advertise the community only to the sessions that named it, and a peer that named none is left out of the fan-out rather than being sent the prefix untagged (agreedSelector, internal/component/bgp/plugins/cmd/announce/blackhole_agreement.go). Section 3.3's origin-validation obligation is met by blackhole-exempt under an RPKI peer. It keeps a BLACKHOLE-tagged route that RFC 6811 makes Invalid on prefix length alone, and only when a covering VRP names the origin AS and the session named the community the route carries (internal/component/bgp/plugins/rpki/blackhole.go). All three consumers read one per-peer answer from one place (internal/component/bgp/blackholecfg). Section 3.2's receiver obligations and the own-Global-Administrator scrub are tracked under RFC 1997 and RFC 7454. Tests bound per requirement in, and the FRR and BIRD interop scenario test/interop/scenarios/59-rfc7999-blackhole-frr asserts the discard route in a real kernel. Both Section 3.3 conditions carry interop evidence as well as unit evidence. That scenario tags RFC7999-3.3-1 and RFC7999-3.3-2 in both polarities. Neither can afterwards fall back to a unit test alone. Enrolled in on 2026-08-13, so the ratchets now hold every requirement here. The walk behind that claim is recorded in prose, with 1 of 4 sites excluded. rfc/requirements/rfc7999.mdrfc/enrolled.txtrfc/extraction/rfc7999.json |
✕ |
| RFC 4360 | Extended communities | Supported | Extended community attribute parsing, encoding, JSON, and policy use. | ✕ |
| RFC 5701 | IPv6 extended communities | ∿ | IPv6 Address Specific Extended Community (code 25) codec: optional-transitive flags, 20-octet per-community encoding, and length-multiple-of-20 parse validation. Tests bound per requirement in. rfc/requirements/rfc5701.md |
One MUST gap, gated in: a malformed code-25 attribute is not treat-as-withdrawn per RFC 7606 (the structural validation pass has no validator for code 25), the same code-25 omission already tracked under RFC 7606 §7.15. The lazy parser still rejects a non-multiple-of-20 length on access. rfc/short/rfc5701.md |
| RFC 8092 | Large communities | Supported | LARGE_COMMUNITY parsing, validation, duplicate removal, JSON, and RFC 7606 length checks. | ✕ |
| RFC 4456 | Route Reflection | Supported | ORIGINATOR_ID, CLUSTER_LIST, route-reflector plugin behavior, cluster checks. | ✕ |
| RFC 7311 | AIGP attribute | ∿ | AIGP wire encoding, decoding, JSON, and set/increment/decrement filters; TLV validation (type-1 length 11, total-length consistency, unknown-TLV preservation) gated per requirement in. rfc/requirements/rfc7311.md |
AIGP is not consumed by best-path selection, and Ze does not strip AIGP at the eBGP administrative-domain boundary (§3.2 MUST NOT): received AIGP is forwarded verbatim as an optional-transitive attribute, removable only by explicit operator policy. Gated as a gap in. rfc/short/rfc7311.md |
| RFC 4364 | BGP/MPLS IP VPNs | Supported | VPNv4 NLRI, RD, labels, route config, encode, and decode. | ✕ |
| RFC 4659 | VPNv6 | ∿ | VPNv6 NLRI (RD + MPLS label + IPv6 prefix) encode/decode, AFI=2/SAFI=128 capability negotiation, and the zero-RD + global-IPv6 24-octet next-hop. | ✕ |
| RFC 8277 | BGP Labeled Unicast | ∿ | IPv4 and IPv6 labeled unicast NLRI (SAFI 4) encode and decode, label stack handling, ADD-PATH framing, route config, Adj-RIB-In label side-data, best-path comparability across differing labels, and dataplane handoff; requirements bound per line in. rfc/short/rfc8277.md |
Ten MUST-level gaps, each annotated in. Multiple Labels Capability (code 8) is absent from ze's capability set (internal/core/bgp/capability/capability.go), so every session is in the RFC 8277 Section 2 single-label mode while the encoders still accept and emit a full stack: RFC8277-2-1, RFC8277-2-3, RFC8277-3.2.2-2. Propagation of an already-multi-label route is unguarded for the same reason: RFC8277-3.2.1-2 -- the prohibition binds on every peer precisely because the capability is negotiated with none, and RFC8277-3.2.1-4 -- no propagation is ever blocked on label count, so the paired withdrawal has no producer either. Encoding: RFC8277-2.1-10 -- the one-octet NLRI Length is written as byte(totalBits) with no bound check (internal/component/bgp/plugins/nlri/labeled/types.go, internal/component/bgp/message/update_build_labeled.go). Reception: RFC8277-2.2-2 and RFC8277-2.4-1 -- the label-stack readers are S-bit-driven (internal/core/bgp/nlri/nlrisplit/labeled.go,:112), so a single-label NLRI with S clear, and a Section 2.4 withdrawal carrying the RECOMMENDED Compatibility value 0x800000, are both read past their prefix and rejected. State: RFC8277-2.5-3 -- label side-data is keyed on the prefix alone (internal/component/bgp/plugins/rib/storage/peerrib.go, familyrib.go), so a second ADD-PATH path overwrites the first path's label binding. Propagation: RFC8277-3.2.2-1 -- ze binds no local label, so a next-hop rewrite re-advertises the upstream label unchanged (internal/component/bgp/reactor/filter_delta_handlers.go). rfc/short/rfc8277.md |
| RFC 3032 | MPLS label stack encoding | Supported as dependency | 20-bit label stack encoding and validation used by labeled unicast and VPN NLRI. | ✕ |
| RFC 4761 | VPLS NLRI | Supported | L2VPN VPLS family registration, encode, decode, and route config. | ✕ |
| RFC 4762 | VPLS architecture | Supported at feature scope | VPLS feature inventory and NLRI support tie the RFC to current behavior. | ✕ |
| RFC 7432 | EVPN | ∿ | EVPN NLRI family (AFI 25 / SAFI 70), common [route-type][length][body] header, and the route-type 1-4 codecs: Route Distinguisher, the 10-octet Ethernet Segment Identifier, Ethernet Tag ID, the 48-bit / 6-octet 802.1Q MAC address, the 0/32/128-bit IP Address Length, Originating Router's IP Address, and the MPLS label stack, plus ADD-PATH framing and the EVPN MP_REACH_NLRI UPDATE builder. Tests bound per requirement in. internal/component/bgp/plugins/nlri/evpn/types.gointernal/component/bgp/message/update_build_evpn.gorfc/requirements/rfc7432.md |
Fifteen MUST gaps annotated in: RFC7432-7.9-1/-2/-3 (the RD is per configured route, with no MAC-VRF binding and no uniqueness check), 8.2.1-1/-2 (no per-ES versus per-EVI Ethernet A-D distinction, so MAX-ET and the zero NLRI label are unenforced), 8.1.1-1 (an Ethernet Segment route accepts any RD type, not only Type 1), 9.2.1-2 (an EVPN route with no configured next hop is advertised with next hop 0.0.0.0 instead of the advertising PE address), 9.2.1-3 (Label1 is operator-supplied, with no local label allocation), and 8.2.1-8 / 9.2.1-4 / 8.4.1-1 / 11.1-2 (route targets are attached only when pre-packed by the caller), 8.2.1-3 and 8.1.1-2 (the Ethernet A-D per ES route and the Ethernet Segment route are both originated -- NewEVPNType1 and NewEVPNType4 in internal/component/bgp/plugins/nlri/evpn/encode.go,:112, reached through the registered in-process route encoder -- but ze attaches no ESI Label extended community to either, and the codec defines no type 0x06 sub-type 0x01 value), plus 11.1-1 (the Inclusive Multicast Originating Router's IP is per route). Sixty-four further MUSTs bind PE roles ze does not play: it is a BGP speaker with no MAC-VRF or EVI model, no bridge or MAC learning, no ARP/ND cache, no EVPN forwarding plane, no ES-Import or MAC Mobility or Default Gateway extended community, no PMSI Tunnel attribute, no designated-forwarder election, and no split-horizon, aliasing, or BUM replication. rfc/short/rfc7432.md |
| RFC 9136 | EVPN IP Prefix (RT-5) | ∿ | RT-5 (IP Prefix) NLRI encode/decode for IPv4 (len 34) and IPv6 (len 58): RD, ESI, Ethernet Tag, prefix-length bounds (<=32/<=128), prefix, gateway, and MPLS label stack, with length-field and prefix-length validation (internal/component/bgp/plugins/nlri/evpn/types.go). | Five MUST gaps annotated in rfc/short/rfc9136.md (RFC9136-3.1-4/3.1-6 ESI/GW-IP zero-unless-overlay-index, 3.2-1 ESI/GW mutual exclusion, 3.1-7/3.1-9 zero-label-without-overlay-index treat-as-withdraw): ze models no overlay index. Five further MUSTs (3.1-8, 3.2-2, 3-1, 3.2-3, 3.2-4) bind the ingress-NVE/PE forwarding, IP-VRF install, and EVPN Router's-MAC Extended Community roles ze does not play. |
| RFC 8955 | IPv4 FlowSpec | ∿ | IPv4 FlowSpec and FlowSpec VPN NLRI encoding, decoding, route config, filters, and action communities: (AFI 1, SAFI 133/134) Multiprotocol capability negotiation, ascending component ordering, first-operator AND-bit handling on encode and decode, single-octet TCP-flags/DSCP/fragment encodings, bitmask and fragment reserved bits ignored on decode, traffic-rate encode rejection of negative rates and decode clamping to zero, traffic-action unused bits zero on encode and ignored on decode, and lowering to the firewall; tests bound per requirement in. rfc/requirements/rfc8955.md |
Six MUST-level gaps, each annotated in: RFC8955-4-3 -- the MP_REACH next-hop length is taken from the configured next-hop instead of being forced to 0 for SAFI 133/134; RFC8955-4.2.1.1-3 -- the numeric-operator decoder keeps reserved bit 4, so an operator carrying it decodes as =; RFC8955-6-1 -- no feasibility validation of a FlowSpec against the unicast RIB; RFC8955-6-2 -- no eBGP leftmost-neighbor-AS enforcement; RFC8955-6-3 -- no revalidation when unicast routes change; and RFC8955-7.5-1 -- the traffic-marking decoder does not mask the two reserved bits above the 6-bit DSCP field, so the marking action is dropped. rfc/short/rfc8955.md |
| RFC 8956 | IPv6 FlowSpec | ∿ | IPv6 FlowSpec and FlowSpec VPN NLRI structural encode/decode (AFI 2, SAFI 133/134): prefix, numeric, bitmask and Flow Label components, round-trip, family registration, and (AFI 2, SAFI 133/134) Multiprotocol capability negotiation; tests bound per requirement in. rfc/requirements/rfc8956.md |
Seven MUST-level gaps, each annotated in: RFC8956-3.1-1 -- the prefix encoder does not zero sub-byte padding beyond the prefix length; RFC8956-3.1-2 -- the decoder retains padding bits without masking; RFC8956-3.1-3 -- offset/length (offset < length <= 128) is not validated and the pattern is encoded from the address start, correct only for offset 0; RFC8956-3.6-1 -- minimal single-octet encoding is incidental, not enforced; RFC8956-3.6-2 -- the Fragment component uses the RFC 8955 IPv4 bitmask layout and does not zero the IPv6 reserved bits on transmit; RFC8956-3.6-3 -- the Fragment decoder reads the raw byte without masking the IPv6 reserved bits; and RFC8956-5-1 -- no Section 5 / RFC 8955 Section 6 flowspec validation-against-unicast procedure is implemented. rfc/short/rfc8956.md |
| RFC 5575 | BGP Flowspec (obsoleted by RFC 8955) | ∿ | IPv4 Flowspec NLRI encode/decode, MP (Code 1) capability negotiation for (AFI 1, SAFI 133/134), traffic-action extended communities, and lowering to the firewall; component ordering and operator/fragment/traffic-marking reserved bits enforced on encode. | Four Section 6 validation MUSTs unmet (same root cause as RFC8956-5-1): 6-1 no eBGP AS_PATH leftmost-neighbor enforcement; 6-2 no feasibility validation against the unicast RIB; 6-3 no flow-spec-vs-best-match originator comparison; 6-4 no more-specific/different-neighbor-AS check. |
| RFC 7752 | BGP-LS | ∿ | Ze is a BGP-LS consumer and transit speaker: Node/Link/Prefix NLRI and node, link and prefix attribute TLV decode (internal/component/bgp/plugins/nlri/ls), (AFI 16388, SAFI 71/72) family registration and Multiprotocol capability negotiation, opaque-key RIB storage that keeps routing universes apart by Identifier, RFC 4760 next-hop encoding, unknown-TLV preservation with byte-identical re-advertisement, and Section 6.2.2 TLV syntactic checks. Requirements bound per line in. rfc/short/rfc7752.md |
Six MUST gaps annotated in: RFC7752-3.1-2 -- LinkDescriptor.WriteTo writes the interface-address TLV before the neighbor-address TLV, so an IPv6 interface address with an IPv4 neighbor address emits TLV 261 before TLV 260; RFC7752-3.1-3 -- repeated same-type TLVs (SRv6 SID 518) are emitted in slice order, unsorted by value; RFC7752-3.3-1 -- the UPDATE decoder reads a type-29 attribute for every address family instead of ignoring it outside link-state; RFC7752-6.2.2-1 -- no RFC 7606 validator is registered for attribute code 29, so a malformed BGP-LS attribute is not attribute-discarded; RFC7752-6.2.6-1 -- the per-family prefix maximum applies to bgp-ls but counts with a CIDR byte-walk that does not parse RFC 7752 TLV NLRI; and RFC7752-8-1 -- ze models no BGP-LS consumer peer, so any peer negotiating bgp-ls has its UPDATEs accepted. All origination obligations (Protocol-ID selection, Identifier stamping, auxiliary Router-IDs, node keying, ASN/BGP-LS-ID uniqueness, OSPF MT-ID bits, MPLS Protocol Mask restriction, prefix attribute reflection) are {not-applicable}: ze derives no link-state from its IS-IS or OSPF and has no BGP-LS config route surface. rfc/short/rfc7752.md |
| RFC 9552 | BGP-LS (obsoletes RFC 7752) | ∿ | Same wire format as RFC 7752 and the same role: ze is a BGP-LS Consumer-side decoder and Propagator, never a Producer. Node/Link/Prefix NLRI and node, link and prefix attribute TLV decode (internal/component/bgp/plugins/nlri/ls), (AFI 16388, SAFI 71/72) family registration and Multiprotocol capability negotiation, unknown NLRI types framed by Total NLRI Length alone and propagated byte-identically under both SAFI 71 and SAFI 72 (GetNLRISizeFunc, internal/component/bgp/message/chunk_mp_nlri.go), unknown and unexpected attribute TLVs preserved, unordered BGP-LS Attribute TLVs accepted as RFC 9552 now requires, no semantic validation on the propagation path, node descriptor sub-TLVs emitted in ascending order, RFC 4760 next-hop encoding, zero-padded TE Default Metric and a 1-octet IS-IS small metric whose two high bits are always zero. Requirements bound per line in. rfc/short/rfc9552.md |
Nine MUST gaps annotated in: RFC9552-5.1-1 -- LinkDescriptor.WriteTo writes the interface-address TLV before the neighbor-address TLV, so an IPv6 interface address with an IPv4 neighbor address emits TLV 261 before TLV 260; RFC9552-5.1-2 -- repeated same-type TLVs (SRv6 SID 518) are emitted in slice order, sorted by neither Length nor Value; RFC9552-5.1-5 -- no NLRI TLV ordering check runs on receipt, so a descending NLRI is propagated rather than treated as malformed; RFC9552-5.2.1.4-1 -- a Node Descriptor carrying two instances of one sub-TLV type is accepted, the duplicate overwriting or appending to the first; RFC9552-8.2.2-4 and RFC9552-8.2.2-5 -- validateMPNLRISyntax is permissive for AFI 16388, so neither the skipable NLRI-discard path nor the non-skipable session-reset path is reached for a broken Link-State NLRI; RFC9552-8.2.2-6 -- no RFC 7606 validator is registered for attribute code 29, so a malformed BGP-LS Attribute is not attribute-discarded; RFC9552-5.3-2 -- an oversized forwarded UPDATE that cannot be split is dropped whole instead of having the BGP-LS Attribute discarded first; and RFC9552-8.2.6-1 -- the per-family prefix maximum applies to bgp-ls but counts with a CIDR byte-walk that does not parse type-length Link-State NLRI. All Producer obligations (Protocol-ID selection, Instance-ID assignment, withdraw-before-modify, link and prefix descriptor composition, MT-ID, OSPF Route Type, auxiliary Router-IDs, MPLS Protocol Mask restriction, Private Use Enterprise Code, unreachable-node re-advertisement) are {not-applicable}: ze derives no link-state from its IS-IS or OSPF and has no BGP-LS origination surface. rfc/short/rfc9552.md |
| RFC 9085 | BGP-LS Segment Routing extensions | ∿ | SR TLVs (SID/Label, Prefix-SID, Adj-SID, SR Capabilities, SRGB/SRLB) decode as part of BGP-LS TLV coverage; the SID/Label 20-bit mask and reserved/undefined-flag fields are ignored on receipt. | Nine origination/encode MUSTs unmet (decode-only plugin, no config surface): the reserved-and-flags-zero-on-transmit and TLV-placement rules have dormant encoders but no origination path; the LAN-Adjacency-SID (TLV 1100) and Range (TLV 1159) TLVs are not implemented at all. |
| RFC 9514 | BGP-LS SRv6 extensions | ∿ | SRv6 End.X SID, Endpoint Behavior, BGP PeerNode SID, and SID Structure TLVs decode as part of BGP-LS SRv6 TLV coverage. | Thirteen origination/encode MUSTs unmet (decode-only plugin, no config surface); the SRv6 Capabilities (TLV 1038) and SRv6 Locator (TLV 1162) TLVs are not implemented at all, and the SID Structure sum-at-most-128 validation is absent on receipt (see rfc/short/rfc9514.md). |
| RFC 9086 | BGP-LS Egress Peer Engineering SIDs | ∿ | PeerNode/Adj/Set SID TLVs (1101-1103) and BGP Router-ID (516) / Member-ASN (517) node descriptors decode as part of BGP-LS TLV coverage; reserved fields ignored on receipt. | EPE SID origination is not implemented: no code instantiates PeerNode/Adj/Set SIDs from live sessions, the BGP-LS plugin registers decode mode only, and there is no config surface to enable or disable EPE advertisement. Same BGP-LS encode gap as RFC 7752. |
| RFC 4684 | Route Target Constraint | ∿ | RTC NLRI decode for display/analysis (ze bgp decode). Tests bound per requirement in. rfc/requirements/rfc4684.md |
Decode-only: no encode/origination path and no RT-membership distribution. Four MUST gaps gated in: Ze does not advertise RT membership NLRI (so no §3.2 Originator/Next-hop or best-path/client-path selection), builds no outbound route filter from RT membership (§3.2), and gates no VPN route advertisement on RT-membership End-of-RIB (§6). rfc/short/rfc4684.md |
| RFC 6514 | MVPN and PMSI Tunnel | ∿ | MVPN NLRI decode, MVPN NLRI encode primitives, config route parser for source-active, shared-tree join, and source-tree join routes, the Section 4 type-and-length NLRI split, opaque Adj-RIB-In storage that split enables, and the RFC 7606 Section 5.4 ruling that discards a route type outside 1. 7 at ingress (RecognizeNLRI). internal/core/bgp/nlri/nlrisplit/mvpn.gointernal/component/bgp/plugins/nlri/mvpn/rfc7606.go |
PMSI_TUNNEL path attribute remains marked not implemented; not all MVPN route types have config builders. |
| RFC 9012 | Tunnel Encapsulation attribute | ∿ | Attribute code 23 parses into Tunnel Type TLVs whose values are kept as raw bytes (internal/core/bgp/attribute/tunnel_encap.go); sub-TLVs are walked on demand with the 1-octet or 2-octet length header the type value selects (tunnel_encap.go); the RFC 9830 Preference sub-TLV is decoded at its mandated 6-octet length (tunnel_encap.go); and every TLV and sub-TLV -- recognized, unrecognized, meaningless for its tunnel type, malformed or duplicated -- is re-advertised byte for byte (tunnel_encap.go). ze originates the attribute for SR Policy: tunnel type 15 carrying preference, MPLS and SRv6 binding SID, priority, weighted segment lists and the policy and candidate-path names (internal/component/bgp/plugins/nlri/srpolicy/config.go). Requirements bound per line in rfc/short/rfc9012.md. | 51 MUST-level gaps annotated in rfc/short/rfc9012.md. No sub-TLV other than Preference is decoded, so every obligation attached to one is unmet: Tunnel Egress Endpoint (RFC9012-3.1-2, RFC9012-3.1-4, RFC9012-3.1-5, RFC9012-3.1-7, RFC9012-3.1-8, RFC9012-13-13, RFC9012-13-14, RFC9012-13-15); VXLAN and NVGRE Encapsulation (RFC9012-3.2.1-3, RFC9012-3.2.1-4, RFC9012-3.2.1-5, RFC9012-3.2.1-6, RFC9012-3.3-1); UDP Destination Port (RFC9012-3.3.2-1); Protocol Type (RFC9012-3.4.1-2, RFC9012-3.4.1-3, RFC9012-3.4.1-4); Color sub-TLV (RFC9012-3.4.2-2); Embedded Label Handling (RFC9012-3.5-1, RFC9012-3.5-2, RFC9012-3.5-4); MPLS Label Stack (RFC9012-3.6-1, RFC9012-3.6-2, RFC9012-3.6-3, RFC9012-3.6-4, RFC9012-3.6-5, RFC9012-3.6-6, RFC9012-3.6-7, RFC9012-3.6-8, RFC9012-3.6-13); Prefix-SID (RFC9012-3.7-2, RFC9012-3.7-3, RFC9012-3.7-4). No tunnel named by the attribute reaches forwarding, so tunnel selection, resolvability and encapsulation obligations are unmet (RFC9012-4.1-2, RFC9012-6-2, RFC9012-7.1-1, RFC9012-8-1, RFC9012-8-2, RFC9012-13-4, RFC9012-13-17). Sub-TLV framing inside a TLV is never validated, so a TLV whose final sub-TLV overruns it is accepted instead of treated as withdraw (RFC9012-13-1, RFC9012-13-2). The attribute and the Encapsulation Extended Community cannot be filtered per session or by default on EBGP sessions (RFC9012-11-1, RFC9012-11-2, RFC9012-11-5, RFC9012-11-6, RFC9012-11-8, RFC9012-11-9, RFC9012-11-10, RFC9012-11-11, RFC9012-15-1). Nine further MUSTs are annotated not-applicable: ze originates no VXLAN, NVGRE, GRE or single-instance sub-TLV and neither reads nor writes the Encapsulation, Router's MAC or Color Extended Communities (RFC9012-3.1.1-3, RFC9012-3.2.1-1, RFC9012-3.2.4-1, RFC9012-4.1-1, RFC9012-4.1-3, RFC9012-4.2-1, RFC9012-4.3-1, RFC9012-10-1, RFC9012-13-6). |
| RFC 8669 | BGP Prefix-SID attribute | ∿ | Attribute code 40 is registered as optional transitive (internal/core/bgp/attribute/attribute.go) and carried end to end: the Label-Index TLV (type 1) and the Originator SRGB TLV (type 3) are encoded from route configuration with their Reserved and Flags octets cleared (internal/component/bgp/config/routeattr_prefixsid.go,:129) and attached to labeled-unicast and VPN UPDATEs (internal/component/bgp/message/update_build_labeled.go); on reception every TLV is bounds-checked with Section 6 attribute-discard for an overrunning TLV length or trailing bytes (internal/component/bgp/message/rfc7606.go), a duplicate attribute is discarded unexamined in favor of the first for PROCESSING purposes (internal/component/bgp/message/rfc7606.go), a duplicate recognized Service TLV never displaces the first (internal/component/bgp/plugins/rib/pool/srv6sid.go), the Section 4 EBGP boundary discards the attribute unless the peer is configured to accept it (internal/component/bgp/reactor/session_validation.go), unknown TLVs and the Reserved/Flags fields are ignored and left byte-identical for propagation, and the label carried in a received labeled-unicast NLRI is the outbound label programmed toward the next hop (internal/core/bgp/nlri/nlrisplit/labeled.go to internal/plugins/fib/kernel/nexthop_linux.go). Requirements bound per line in rfc/short/rfc8669.md. | Eleven MUST gaps annotated in rfc/short/rfc8669.md. Duplicate attribute, wire half: RFC8669-6-2 -- a skipped duplicate Prefix-SID is not removed from the bytes forwarded on. A valid first occurrence records no DiscardEntry and ApplyAttrDiscard returns the attributes untouched (internal/component/bgp/message/attr_discard.go); when the first occurrence is the malformed one, applyInPlace tombstones only it, because AttrFind returns the first match (internal/component/bgp/message/attr_discard.go, internal/core/bgp/attribute/iterator.go). The other ten are in the SR-MPLS label-index semantics ze does not implement: RFC8669-3.1-1/4.1-1/4.1-2 -- the Label-Index TLV is never required nor looked for, so a labeled-unicast Prefix-SID without one is accepted instead of being considered "invalid"; RFC8669-4.1-3 -- no label-index-to-prefix reverse index exists, so the "conflicting" state is never detected; RFC8669-4.1-4/4.1-6 -- with neither state computed, the ignore action and the Section 6 routing of an "invalid" attribute have no trigger; RFC8669-4.1-5 -- ze allocates no local (dynamic) label for a BGP prefix, the programmed label is always the one received in the NLRI; RFC8669-4.1-7 -- an implicit-NULL (3) label in the NLRI is programmed as an MPLS push rather than a pop (internal/plugins/fib/kernel/mpls.go, internal/plugins/fib/kernel/nexthop_linux.go); RFC8669-5.1-1 -- the advertised NLRI label comes from route configuration and no matching incoming MPLS entry is programmed, only LDP and RSVP-TE emit MPLS ingress state; and RFC8669-8-1 -- the AS boundary is gated on ingress only, egress propagation of a locally originated or IBGP-learned Prefix-SID to EBGP peers has no configuration gate. |
| RFC 9252 | BGP SRv6 Service TLV (Prefix-SID) | ∿ | Prefix-SID attribute (code 40) SRv6 L3/L2 Service TLV parse, SID Information Sub-TLV and SID Structure Sub-Sub-TLV extraction with transposition and the errata-7817 sum bound (internal/component/bgp/plugins/rib/pool/srv6sid.go); malformed-Service-TLV treat-as-withdraw (internal/component/bgp/message/rfc7606.go); no-valid-SID best-path ineligibility (internal/component/bgp/plugins/rib/rib_bestchange.go); next-hop-change Prefix-SID strip vs next-hop-unchanged preserve (internal/component/bgp/reactor/peer_forward_facts.go); and SRv6 Prefix-SID config encode with zeroed reserved/flags octets (internal/component/bgp/config/routeattr_prefixsid.go). Requirements bound per line in rfc/short/rfc9252.md. | Eight MUST gaps annotated in rfc/short/rfc9252.md: RFC9252-4.1-1/6.1-1/6.2-1 -- Transposition Length is never bounded against the 20-bit VPN / 24-bit EVPN label field or against FL/AL; RFC9252-3.2.1-1/3.2.1-2 -- the zero-offset-when-length-zero and zero-when-scheme-not-applicable transposition constraints are not enforced; RFC9252-3.2-5/3.2-6 -- ze keeps no SRv6 Endpoint Behavior registry, so it neither ignores SIDs with a non-zero Argument Length under an unknown behavior nor validates AL against a known behavior; and RFC9252-5-2 -- ze installs the received Service SID into the FIB (kernel SEG6 encap at internal/plugins/fib/kernel/nexthop_linux.go, VPP SR steering at internal/plugins/fib/vpp/srv6.go) but performs no Section 5 resolvability check on the SID locator before best-path computation (isSRv6Ineligible gates on extraction validity only). |
| RFC 9830 | BGP SR Policy | ∿ | Ze originates and carries SR Policy: the SAFI 73 NLRI is written with the mandated 96-bit or 192-bit length for AFI 1 and AFI 2 (internal/component/bgp/plugins/nlri/srpolicy/types.go), parsed back (types.go) and split on the wire (split.go), and every candidate-path sub-TLV is encoded into one Tunnel Type 15 TLV of the Tunnel Encapsulation attribute -- preference, MPLS and SRv6 binding SID, priority, weighted segment lists of Type A and Type B segments with the SRv6 Endpoint Behavior and SID Structure, and the policy and candidate-path names (internal/component/bgp/plugins/nlri/srpolicy/config.go). Each sub-TLV carries its mandated value length with zero Flags and RESERVED octets, and the MPLS TC, S and TTL bits are zero. On receipt the attribute is kept as raw TLV bytes and re-advertised octet for octet, with the Preference sub-TLV decoded at its mandated 6-octet length (internal/core/bgp/attribute/tunnel_encap.go, :104, :145). Requirements bound per line in rfc/short/rfc9830.md. | 20 MUST-level gaps annotated in rfc/short/rfc9830.md. Encoding defects: the Binding SID Flags octet is written as 0x10, a bit Section 2.4.2 leaves unassigned (RFC9830-2.4.2-5); a reserved MPLS label value 0-15 is accepted as a binding SID (RFC9830-2.4.2-11); SID-structure lengths totalling more than 128 are accepted (RFC9830-2.4.4.2.4-4); and an SR Policy advertisement carries neither a route target nor NO_ADVERTISE (RFC9830-4.1-2). Receive-side validation is absent: SAFI 73 is skipped by the RFC 7606 NLRI check and attribute 23 has no validator, so nothing is treated as withdraw for a wrong or duplicated tunnel type, a bad NLRI length, a missing route target or NO_ADVERTISE, a malformed sub-TLV or a malformed attribute (RFC9830-2.2-1, RFC9830-2.2-3, RFC9830-4.2.1-1, RFC9830-4.2.1-3, RFC9830-4.2.1-4, RFC9830-4.2.1-5, RFC9830-4.2.1-8, RFC9830-5-1, RFC9830-5-2, RFC9830-5-4, RFC9830-5-5, RFC9830-5-6, RFC9830-5-7, RFC9830-5-8). Propagation is family-generic: NO_ADVERTISE is not honored on egress and there is no eBGP-by-default block for SAFI 73 (RFC9830-4.2.3-1, RFC9830-4.2.3-2). Twelve further MUSTs are annotated not-applicable: ze implements no ENLP sub-TLV, no color-based steering and no SRPM, so it never instantiates, selects or deletes a candidate path. |
| RFC 9256 | Segment Routing Policy Architecture | ∿ | Ze is an SR Policy originator, not a headend: it builds the SR Policy identification tuple into the NLRI key (internal/component/bgp/plugins/nlri/srpolicy/types.go), parses it back (types.go), and encodes a candidate path's preference, priority, MPLS and SRv6 binding SID, weighted segment lists of Type A and Type B segments, and the policy and candidate-path names into the Tunnel Encapsulation attribute (config.go). Symbolic names stay out of the NLRI key, and the segment encoder admits only Type A and Type B. Requirements bound per line in rfc/short/rfc9256.md. | Six MUST gaps annotated in rfc/short/rfc9256.md, all from ze holding no SR Policy state: RFC9256-2.6-1 -- the only candidate-path identity is the RFC 9830 distinguisher in the NLRI key and no candidate-path store resolves add, delete or modify; RFC9256-4-6 -- no code turns a segment list into a label stack or an SRv6 SID list, and a received Tunnel Encapsulation attribute stays raw TLV bytes with only the Preference sub-TLV decoded (internal/core/bgp/attribute/tunnel_encap.go); RFC9256-5.1-2 and RFC9256-5.1-5 -- parseSegmentList determines no segment-list validity (empty list, weight 0 and mixed SR-MPLS/SRv6 lists are all accepted) and ze resolves no SID, so first-SID reachability is never established; and RFC9256-6.1-3 and RFC9256-6.2-4 -- the binding SID is encoded from configuration with no allocation table, no SRLB availability check and no alert. Headend obligations (composite candidate paths, Originator sub-TLV, candidate-path selection, active-path forwarding, dynamic candidate paths, Specified-BSID-only, policy state reporting) are annotated not-applicable: ze instantiates no policy. |
BGP operations, telemetry, RPKI, and BFD-adjacent standards
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 7854 | BMP v3 | ∿ | BMP receiver and sender, peer lifecycle, route monitoring messages, CLI and config. Each collector connection is a complete BMP session: Initiation first, then a Peer Up for every BGP peer that is already established, and a Termination before the session is closed (sent by the teardown path that closes the socket, so a collector actually receives it). Messages reach the socket through a per-session byte-bounded transmit queue drained by that session's own goroutine, so a collector that stops reading never blocks BGP; when the bound is reached the session is reset with a bare TCP close and no Termination. | Loc-RIB route monitoring is provided under RFC 9069. |
| RFC 8671 | BMP Adj-RIB-Out | Supported within BMP sender scope | Adj-RIB-Out direction flag handling and sent-route monitoring. | ✕ |
| RFC 9069 | BMP Loc-RIB monitoring | Supported | Loc-RIB Instance Peer (Peer Type 3): per-peer flags cleared, empty sent/received OPENs, zero Address and Peer AS, Peer BGP ID set to the local router-id, exactly one Loc-RIB Peer Up per RIB instance per BMP session, and a full-table dump requested both when monitoring starts and whenever a collector connects, closed with an RFC 4724 End-of-RIB marker for EVERY base unicast family the dump owes one for -- the families it carried and the families the RIB stayed silent about alike (RFC 9069 itself specifies no End-of-RIB; the marker is ze's own dump-complete signal, in the RFC 4724 Section 2 form, and Section 4 of that RFC owes it "including the case when there is no update to send" for a family). Each dump also carries a per-dump correlation token, so a replay another subsystem requested is never mis-claimed as this plugin's dump. Tests bound per requirement in. rfc/requirements/rfc9069.md |
None outstanding. Closed 2026-07-27: (1) the mixed-table gap -- a table whose IPv4 half was empty used to get an IPv6 marker and no IPv4 one, because the RIB emits no batch for an empty family (internal/component/bgp/plugins/rib/rib_bestchange.go), leaving a collector waiting on IPv4 waiting forever for a dump that had finished; closeDumpFamilies now closes every owed family (TestMixedFamilyDumpClosesTheSilentFamily). (2) A replay requested by sysrib that landed while a dump was in flight was claimed as this plugin's: the other collectors silently lost the batch and were sent an End-of-RIB for a dump they never requested. The claim is now made on a per-dump token the RIB echoes back (TestForeignReplayIsNotClaimedAsOurDump). |
| RFC 6811 | RPKI origin validation | Supported | Origin validation states (Valid/Invalid/NotFound) from ROA/VRP lookup with four-octet ASN support, RTR transport, an operator-configurable per-state action (invalid-action reject/log-only/accept, default reject) so exclusion of Invalid routes is an explicit policy choice, automatic re-validation of installed routes when the VRP set changes, and a fail-open guard. | ✕ |
| RFC 8210 | RPKI RTR v1 | ∿ | ze is the RTR router (client). It opens every connection with a Reset or Serial Query (internal/component/bgp/plugins/rpki/rtr_session.go), speaks version 2 with a downgrade to version 1 (rtr_pdu.go, rtr_session.go), parses IPv4/IPv6 Prefix, Cache Response, the 24-byte v1 End of Data with its Refresh/Retry/Expire parameters, Cache Reset, Serial Notify and Error Report PDUs, enforces Max Length >= Prefix Length and ignores reserved Flags bits on receipt (rtr_pdu.go), applies each delta atomically at End of Data (rtr_session.go), ignores Serial Notify during startup, re-issues a Reset Query on Cache Reset and on No Data Available, and validates origins against the merged VRP cache (validate.go). | Twelve MUST-level gaps, each annotated in rfc/short/rfc8210.md: RFC8210-5.1-3, RFC8210-5.1-4 -- no Session ID mismatch detection, no Error Report code 0, no cache flush (rtr_session.go adopts the Session ID unconditionally); RFC8210-5.1-5 -- a version downgrade reuses the previous version's Session ID and serial (rtr_session.go); RFC8210-7-3, RFC8210-7-4, RFC8210-7-7 -- handlePDU never reads hdr.Version (rtr_session.go), so no received-version check, downgrade or session drop happens; RFC8210-12-1 -- Error Code 4 is excluded from isFatalError (rtr_pdu.go) and at rtrVersionMin the session survives it (rtr_session.go); RFC8210-5.10-2 -- Router Key PDUs are discarded (rtr_session.go), so no Subject Public Key comparison exists; RFC8210-6-2 -- VRPs outlive the Expire Interval (ROACache.Clear, roa_cache.go, has no production caller); RFC8210-4-1 -- no most-preferred-cache selection (rpki.go runs every configured cache concurrently); RFC8210-10-2 -- VRPs are not marked by source cache (rpki.go, roa_cache.go); RFC8210-9-3 -- no protected RTR transport (rtr_session.go dials only unprotected TCP). |
| RFC 6810 | RPKI-to-Router Protocol v0 (RPKI-RTR) | ∿ | ze is the RTR router (client): it parses IPv4/IPv6 Prefix, Cache Response, End of Data, Cache Reset, Serial Notify and Error Report PDUs (internal/component/bgp/plugins/rpki/rtr_pdu.go, rtr_session.go), sends Reset/Serial Queries, rejects a Prefix PDU whose Max Length is below its Prefix Length on receipt, validates route origins against the merged VRP cache (validate.go), and re-issues a Reset Query on Cache Reset and No Data Available. The v0 wire is reached through these shared router behaviors; ze negotiates RTR v1/v2 (min version 1). | Four MUST-level gaps, each annotated in rfc/short/rfc6810.md: RFC6810-5.1-2 -- no Session ID mismatch detection or cache flush (rtr_session.go adopts the cache Session ID unconditionally); RFC6810-4-1 -- no most-preferred-cache selection (rpki.go runs every configured cache concurrently); RFC6810-8-1 -- VRPs are not marked by source cache (one shared ROACache, rpki.go, roa_cache.go); RFC6810-7-3 -- no protected RTR transport (rtr_session.go dials only unprotected TCP, with no SSH/TLS/TCP-MD5/TCP-AO client). |
| RFC 9582 | RPKI RTR v2 ASPA records | Supported | ASPA PDU cache, full replacement semantics, RTR v2 negotiation. | ✕ |
| RFC 5880 | BFD base | ∿ | Control packet codec and the Section 6.8.6 structural reception checks, the Section 6.8.6 transition table, Section 6.8.1 state variables, Active/Passive roles, slow start and the Poll/Final sequence, the D-bit guard, detection and echo timers with Section 6.8.7 jitter, Keyed and Meticulous Keyed MD5/SHA1 authentication, echo scheduling and demultiplexing, the single-hop TTL gate, metrics and show commands; MUST-level requirements bound per requirement in. rfc/requirements/rfc5880.md |
Fourteen MUST gaps, gated in: Demand mode is not driven -- bfd.DemandMode has no writer and the stored remote D bit is never read, so no Poll is raised on a D-bit or content change and periodic transmission is never suppressed (RFC5880-6.6-2, 6.6-3, 6.8.6-14, 6.8.7-7, 6.8.17-1); periodic transmission also continues when bfd.RemoteMinRxInterval is zero (RFC5880-6.8.7-6); the two timing changes the RFC excepts from immediate effect are applied immediately instead of at Poll termination (RFC5880-6.8.3-3, 6.8.3-4); bfd.XmitAuthSeq starts at zero rather than a random value and bfd.AuthSeqKnown is never cleared after twice the Detection Time (RFC5880-6.8.1-11, 6.8.1-13); the meticulous replay check accepts any strictly greater sequence rather than exactly RcvAuthSeq+1 (RFC5880-6.7.3-10); there is no forwarding-plane-reset hook, so diagnostic 4 has no producer (RFC5880-6.8.15-1); and no congestion-control mechanism governs the transmit rate (RFC5880-7-1, 7-2). Simple Password authentication and Final-packet rate limiting are absent by design and annotated not-applicable. IPv6 transport coverage is tracked with BFD. rfc/short/rfc5880.md |
| RFC 5881 | BFD single-hop | ∿ | Single-hop UDP 3784 Control sessions, TTL/GTSM receive gate (TTL=255) plus TTL=255 transmit, first-packet demux by remote address/interface/protocol, Your-Discriminator demultiplexing, Active-role default, stable transmit destination, per-protocol sessions, and echo on UDP 3785; MUST-level requirements bound per requirement in. rfc/requirements/rfc5881.md |
Six MUST gaps, gated in: RFC5881-4-2 -- the control socket transmits from port 3784 rather than an ephemeral 49152-65535 source port (single-socket design, internal/component/bfd/transport/udp.go); RFC5881-2-4/4-6/4-7 -- the echo function is application-level ZEEC reflection to the peer (internal/component/bfd/engine/echo.go) rather than the RFC self-addressed, forwarding-plane-looped echo, so self-addressing, forward-back destination, and redirect-avoiding source are unimplemented; and RFC5881-6-3/6-4 -- point-to-point links are not modelled distinctly, so the first-packet demux keys on the peer source address (internal/component/bfd/engine/loop.go) instead of accepting any initial source. IPv6 dual-bind and wider deployment proof remain tracked with BFD. rfc/short/rfc5881.md |
| RFC 5882 | BFD generic application | ∿ | BFD integration model used by BGP and static next-hop tracking. | Same BFD partial status. |
| RFC 5883 | BFD multi-hop | ∿ | Multi-hop UDP 4784 sessions, single-hop/multihop port separation (3784/4784), Active/Passive roles, echo-on-multihop rejection, and min-TTL floor; tests bound per requirement in. rfc/requirements/rfc5883.md |
✕ |
| RFC 9384 | BFD-triggered BGP Cease | Supported within BFD | BGP peer opt-in sends Cease subcode 10 when BFD reports forwarding path loss. | ✕ |
IS-IS, OSPF, MPLS, and traffic engineering
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 1195 | Integrated IS-IS for IPv4 | Experimental | Native IS-IS over Layer 2, L1/L2, broadcast and point-to-point circuits, TLV 132. | Feature remains Experimental pending production hardening and deployment evidence. |
| RFC 5301 | IS-IS dynamic hostname | Supported | Dynamic hostname TLV 137 origination and decode (writeHostnameTLV, hostnameTLV). The advertised value is refused at the config boundary unless it is printable 7-bit ASCII and its labels satisfy RFC 2181 section 11. ISISHostnameValidator enforces that, reached through the ze:validate extension on the hostname leaf in. Nothing sanitizes or converts the value on the way to the wire. The name an operator configures is the name a peer reads. sanitizeHostname drops every octet outside 0x20. 0x7e from a RECEIVED value before display, which keeps a peer's malformed advertisement out of the CLI. Obligations extracted 2026-07-30 and bound per line in. The walk is recorded in. internal/plugins/isis/packet/tlv_core.gointernal/plugins/isis/lsdb/encode.gointernal/component/config/validators.gointernal/plugins/isis/yang/ze-isis-conf.yanginternal/plugins/isis/show.gorfc/short/rfc5301.mdrfc/extraction/rfc5301.json |
Enrolled 2026-08-10. All seven gated obligations of section 3 are proven in both polarities, and no row carries {gap} or {not-applicable}. The IDNA sentence of section 3 is conditional on a user-interface that permits Unicode characters. Ze's refuses it. The antecedent is false, so no ToASCII conversion is owed. That reading is recorded in beside the requirement. The receive path stays lenient on purpose. RFC 5301 section 4 lets a receiver ignore or install the mapping. Rejecting a peer's LSP over its hostname octets would be a denial-of-service lever. rfc/short/rfc5301.md |
| RFC 5303 | IS-IS point-to-point three-way | Experimental | Point-to-point IIH three-way handshake: TLV 240 (Point-to-Point Three-Way Adjacency) origination and decode, the mandatory Adjacency Three-Way State and Extended Local Circuit ID fields, the neighbor System ID echo that gates the adjacency to Up, and the legacy two-way fall-back for peers that omit the option. Tests bound per requirement in. rfc/requirements/rfc5303.md |
Gaps gated in: the Extended Local Circuit ID is derived as uint8(ifindex) so it is not unique beyond 256 interfaces (RFC5303-3.2-4); the Neighbor Extended Local Circuit ID is echoed as 0 and never examined (RFC5303-3.2-6, RFC5303-3.2-8); an invalid three-way state is not discarded (RFC5303-3.2-7); the loop-detection neighbor-mismatch discard is absent (RFC5303-3.2-9); and Ze derives the three-way state from the ISO 10589 adjacency state instead of the distinct Section 3.2 state table, so the "Accept" and restart "Down" actions are not modeled (RFC5303-3.2-11, RFC5303-3.2-12). rfc/short/rfc5303.md |
| RFC 5304 | IS-IS HMAC-MD5 authentication | Experimental | Interface and level key-chain authentication. | Same IS-IS experimental status. |
| RFC 5310 | IS-IS generic crypto authentication | Experimental | HMAC-SHA authentication path. | Same IS-IS experimental status. |
| RFC 5305 | IS-IS wide metrics and TE TLVs | Experimental | Extended IS reachability (TLV 22) and extended IPv4 reachability (TLV 135) with wide metrics and the up/down bit; unknown sub-TLVs retained; tests bound per requirement in. rfc/requirements/rfc5305.md |
RFC5305-3-1 gap: a link advertised with the maximum link metric (2^24-1 = 16777215) is still used in normal SPF (spf/graph.go builds the edge with the raw metric and spf/spf.go excludes only metrics at or above MaxPathMetric 0xFE000000), so the Section 3 maximum-link-metric exclusion is not implemented. TE sub-TLVs (6/8) and TLV 134 (TE Router ID) are not implemented (no IS-IS TE). |
| RFC 3787 | IS-IS interoperability guidelines | ∿ | Obsolete TLV 131/133 are ignored on receipt (no codec decoder, opaque passthrough, TLV 133 never treated as auth); the originated IIH carries the Protocols Supported TLV (129, IPv4 NLPID) and IP Interface Address TLV (132) for mixed-environment interoperability. Tests bound per requirement in. rfc/requirements/rfc3787.md |
One MUST gap, gated in: Ze originates ONLY wide metrics (TLV 22/135) and never the narrow TLV 2, so it does not fall back to narrow-metric origination for a mixed narrow/wide domain -- it requires every device to be wide-capable. Ze still DECODES a legacy neighbor's narrow TLV 2. rfc/short/rfc3787.md |
| RFC 2966 | IS-IS up/down bit | Experimental | Up/down bit retention and redistribution behavior. | Same IS-IS experimental status. |
| RFC 5308 | IS-IS IPv6 | Experimental | IPv6 reachability over the same IS-IS instance. | Same IS-IS experimental status. |
| RFC 5120 | IS-IS multi-topology | Unsupported | None; IS-IS runs single-topology dual-stack only. | Non-congruent IPv4/IPv6 topologies are out of scope; multi-topology TLVs are not implemented. |
| RFC 2328 | OSPFv2 | ∿ | Native OSPFv2 engine, raw protocol 89: the 24-byte common header with Version-2 validation and the auth-excluding packet checksum, the Fletcher LS checksum, the Section 13 flooding procedure (checksum/unknown-type discard, stub-area Type-5 filter, Exchange-or-higher gate, Section 13.1 freshness ordering, MaxAge+MaxSequenceNumber silent discard, retransmission lists at RxmtInterval, Table 19 acknowledgment decisions, self-originated re-origination and premature-aging flush), Section 14 aging and purge retention, the Section 16 routing calculation (two-way check, ABR backbone-only summaries, LSInfinity/MaxAge/self skips, intra-over-inter-over-external path preference), Database Exchange with a single outstanding DD and the BadLSReq restart, virtual links with Interface MTU 0 in their DDs, positive interface output cost, and Appendix D authentication types 0/1/2 including the non-decreasing cryptographic sequence number. Requirements bound per line in. rfc/short/rfc2328.md |
One MUST gap, annotated in and gated by make ze-rfc-check: RFC2328-13.3-2 -- the InfTransDelay increment of LS age is applied on retransmission (internal/plugins/ospf/lsdb/flooding.go) and on a direct database-copy reply (internal/plugins/ospf/lsdb/flooding.go), but the normal flood path copies the LSA with transmit delay 0 (floodExcept -> entry.LSA(d.now) -> Raw(now, 0), internal/plugins/ospf/lsdb/flooding.go and internal/plugins/ospf/lsdb/entry.go), so a first-flooded LSA carries an unincremented age. The feature also remains pre-production pending hardening and deployment evidence. rfc/short/rfc2328.md |
| RFC 3101 | OSPF NSSA | Experimental | Type-7 origination/flooding, redistribution into NSSA, N/E-bit Hello negotiation, Router-LSA Nt/E/B flags, translator election (Nt-bit candidates, highest-RID, always/never roles, stability grace), Type-7 to Type-5 translation with FA/metric/tag preservation and highest-RID duplicate suppression, source preference, Type-3 summary import policy. For OSPFv2 additionally: mandatory border-router defaults with no operator gate, and no-summary Type-3 defaults. For both address families: the P-bit and suppressed-summary-import gates on installing a received Type-7 default. | The Section 2.4 default-route requirements are proven for OSPFv2 and incomplete for OSPFv3, tracked by. RFC3101-2.4-4: an OSPFv3 NSSA border router reaches the OSPFv2 producer, so its Type-7 default is keyed 0x0007 rather than the OSPFv3 0x2007 (the install half of that requirement is address-family-neutral and does hold). RFC3101-2.4-5: an OSPFv3 no-summary NSSA receives no default at all, because the ::/0 inter-area default is injected for stub areas only. OSPFv2 origination is proven against FRR by test/interop/scenarios/ospf-stub-nssa-frr. Note that the checklist records both as tested: its requirement ids carry no address-family dimension, so a tagged test on the OSPFv2 path satisfies it for both. Same OSPF experimental status. plan/spec-ospf-rfc3101-nssa-defaults.mdrfc/short/rfc3101.md |
| RFC 5709 | OSPFv2 HMAC-SHA authentication | Experimental | OSPFv2 cryptographic authentication path. | Same OSPF experimental status. |
| RFC 7474 | OSPFv2 manual-key security extension | Experimental | Cryptographic authentication with per-packet-type replay protection (RFC 7474 defines no OSPFv2 authentication trailer; that construct is OSPFv3/RFC 7166). | Same OSPF experimental status. |
| RFC 5340 | OSPFv3 (partial, experimental) | ∿ | Native OSPFv3 as an IPv6 address-family engine sharing the OSPFv2 reactor through the codec seam: the 16-byte common header with Version-3 validation, the IPv6 upper-layer checksum bound to the datagram source/destination, the per-interface Instance ID demux, raw IPv6 protocol 89 with a link-local source and the ff02::5 / ff02::6 groups, per-link (not per-subnet) operation with Router-ID neighbor identity, the scope-typed LS Type registry with link-local-scope Link-LSAs kept on their own link, address-free Router-LSAs and Network-LSAs listing every fully adjacent router, Link-LSAs and Intra-Area-Prefix-LSAs carrying the word-padded prefix encoding, Inter-Area-Prefix / Inter-Area-Router / AS-External / NSSA LSAs, global-scope-only virtual-link endpoints, and the Appendix C.3 positive cost / InfTransDelay and matching HelloInterval / RouterDeadInterval checks. Requirements bound per line in. rfc/short/rfc5340.md |
Five MUST gaps, annotated in and gated by make ze-rfc-check. RFC5340-2.5-2: intra-area-prefix-LSAs exclude link-local addresses, but the ABR inter-area summary path (internal/plugins/ospf/origination_v6_summary.go) and the ASBR redistribution path (internal/plugins/ospf/origination_v6_external.go) apply no link-local filter. RFC5340-2.8-2: the SPF graph keys a router vertex by Advertising Router and assigns rather than concatenates, so a router that spreads its links across several Router-LSAs is aggregated only to its last one (internal/plugins/ospf/afstrategy_v6.go). RFC5340-4.2.2-1: no destination-address acceptance check -- the datagram destination is used only as checksum pseudo-header input (internal/plugins/ospf/dispatcher.go). RFC5340-4.9-1 and RFC5340-4.9-2: the §4.9 multiple-interfaces-to-one-link model (Active/Standby, shared Interface Instance ID, standby link-local LSA flush) has no producer. The feature also remains pre-production pending hardening and deployment evidence. rfc/short/rfc5340.md |
| RFC 5838 | OSPFv3 multiple address families | Experimental | AF-bit set in Hello/DD Options plus the AF-bit adjacency gate (non-default AF requires it, base IPv6-unicast AF ignores it), the RFC 5838 §2.1 Instance-ID-range to address-family mapping with one per-AF engine owning its LSDB/neighbors/SPF/install-family, IPv4-over-OSPFv3 prefix decode and route build, IPv4 forwarding-address encode/decode for AS-external and NSSA LSAs, and global-IPv6 virtual-link endpoints; requirements gated in make ze-rfc-check. rfc/short/rfc5838.md |
Eight MUST gaps annotated in: the AF-bit is not set in originated LSAs (RFC5838-2.2-1); an IPv4-AF Link-LSA carries no IPv4 link-local address (RFC5838-2.5-1); and the RFC 5838 §2.7 per-address-family MTU handling is unimplemented -- no separate AF-versus-IPv6 MTU, no IPv4 interface MTU, and no M6-bit (RFC5838-2.7-1, 2.7-2, 2.7-3, 2.7-4, 2.7-8, 2.7-11). Feature remains under OSPF experimental status. rfc/short/rfc5838.md |
| RFC 7166 | OSPFv3 authentication trailer | Unsupported | None: the OSPFv3 Authentication Trailer wire format is not implemented. OSPFv3 carries only the transmit-side checksum-omission hooks (internal/plugins/ospf/v3/packet/checksum.go, header.go); an interface authentication key-chain drives the separate OSPFv2 cryptographic path (RFC 2328/5709/7474) through internal/plugins/ospf/packet/auth_verify.go, not an OSPFv3 trailer. |
The OSPFv3 Authentication Trailer is absent: no AT-bit in the OSPFv3 Options, no 16-byte trailer (Auth Type / Auth Data Len / SA ID / 64-bit sequence / HMAC), no Security Association, no IPv6-source-bound digest, and no receive-side trailer verification or per-packet-type replay for OSPFv3. All 17 MUST-level requirements are annotated {gap} in. rfc/short/rfc7166.md |
| RFC 4552 | OSPFv3 IPsec authentication | Experimental | Manual AH/ESP IPsec config for OSPFv3 IPv6 interfaces, XFRM readiness check, SA and policy lifecycle. | Manual keying only; feature remains under OSPF experimental status. |
| RFC 4577 | OSPF as the PE/CE protocol for BGP/MPLS IP VPNs | Not supported | None of the PE-side procedures: ze has no VRF, no per-VRF OSPF instance, no Domain Identifier / OSPF Route Type / OSPF Router ID extended community, no DN-bit setting or checking, no VPN Route Tag and no sham link. What exists is the OSPF machinery the spec builds on: independent OSPF instances per RFC 6549 Instance ID (internal/plugins/ospf/multi_instance.go,68), Type 3 / Type 5 / Type 7 origination with a configurable external route tag (internal/plugins/ospf/redist_wiring.go, internal/plugins/ospf/lsdb/origination.go), OSPF cryptographic authentication (internal/plugins/ospf/auth_keystore.go, internal/plugins/ospf/packet/auth_verify.go), virtual links (internal/plugins/ospf/virtual_link.go), and an OSPF-to-BGP redistribution export carrying only route-table prefixes (internal/plugins/ospf/redistribute/source.go). Requirements bound per line in. rfc/short/rfc4577.md |
Twenty gaps, annotated in and gated by make ze-rfc-check: RFC4577-4.1.1-1, RFC4577-4.2.1-1, RFC4577-4.2.1-2, RFC4577-4.2.4-1 and RFC4577-4.2.4-2 (independent instances exist, internal/plugins/ospf/multi_instance.go,68, but nothing associates one with an OSPF domain, a VRF or a Domain Identifier, and no configuration surface offers such an association); RFC4577-4.2.5.1-1, RFC4577-4.2.5.1-2 and RFC4577-4.2.8.1-1 (OptionDN is defined and encodable, internal/plugins/ospf/types/options.go, but no originator sets it); RFC4577-4.2.6-3 (no receive-side DN check, internal/plugins/ospf/spf/external.go); RFC4577-4.2.6-4 and RFC4577-4.2.5.2-7 (the External Route Tag is decoded, internal/plugins/ospf/packet/lsa_external.go, and is copied through by the NSSA Type 7 -> Type 5 translator, internal/plugins/ospf/nssa.go, but no receive-side producer ever tests it -- the route calculation never reads it, internal/plugins/ospf/spf/external.go); RFC4577-4.2.5.1-3, RFC4577-4.2.5.2-2, RFC4577-4.2.5.2-3, RFC4577-4.2.5.2-6, RFC4577-4.2.5.2-8 and RFC4577-4.2.8.1-2 (a redistribution route tag is configurable and placed in Type 5 LSAs, internal/plugins/ospf/redist_wiring.go,169-176, but it defaults to 0 and is not a VRF-scoped VPN Route Tag); RFC4577-4.2.5.2-9 (Type 5 origination makes ze an ASBR, LSDB.SelfIsASBR at internal/plugins/ospf/lsdb/origination.go, but no domain test scopes it); RFC4577-4.2.6-8 (the OSPF distance reaches the redistribution event, internal/plugins/ospf/redistribute/source.go, then is dropped before the BGP announcement, internal/component/bgp/plugins/redistribute_egress/redistribute.go); RFC4577-4.2.8.1-3 (one shared DefaultExternalMetric, internal/plugins/ospf/config.go). The remaining thirty-four requirements are recorded {not-applicable} with the grep proving no producer exists. rfc/short/rfc4577.md |
| RFC 5250 | OSPFv2 opaque LSAs | Experimental | Opaque LSA framework and retention. | Same OSPF experimental status. |
| RFC 3630 | OSPFv2 Traffic Engineering LSA | Experimental | TE LSA body and sub-TLV support. | Same OSPF experimental status. |
| RFC 5392 | OSPF inter-AS TE (OSPFv2) | Experimental | OSPFv2 Inter-AS-TE-v2 (Opaque type 6): Remote-AS (21), IPv4/IPv6 Remote-ASBR-ID (22/24) sub-TLVs; Link-ID prohibition and Remote-AS requirement enforced on originate and receive; MinLSInterval-paced proxy origination with no adjacency or Hellos. | Four MUST gaps: the OSPFv3 Inter-AS-TE-v3 LSA (function code 13) is unimplemented, so the U-bit=1 rule (RFC5392-3.1.2-1), the v3 Neighbor-ID prohibition (RFC5392-3.2.1-2), and the v3 IPv6/IPv4 Remote-ASBR-ID inclusion rules (RFC5392-3.3.3-1, RFC5392-3.3.3-2) have no v3 carrier to bind. |
| RFC 7770 | OSPF Router Information LSA | Experimental | Router Information LSA body and multi-instance ordering. | Same OSPF experimental status. |
| RFC 7684 | OSPF Extended Prefix and Link LSAs | Experimental | Extended Prefix and Extended Link LSA bodies and malformed TLV handling. | Same OSPF experimental status. |
| RFC 3623 | OSPFv2 Graceful Restart | Experimental | Restarter (planned + opt-in unplanned) and helper behavior; Grace-LSA Opaque type-3 body codec shared with RFC 5187. | Two MUST gaps gated in rfc/short/rfc3623.md: the helper does not preserve the transit-area V-bit when helping over a virtual link (RFC3623-3-1), and helper entry does not refuse on a changed retransmission-list LSA (RFC3623-3.1-1, onGraceReceived is hardcoded permissive, mitigated by the Section 3.2 strict-LSA-checking exit). Experimental pending deployment hardening. |
| RFC 5187 | OSPFv3 Graceful Restart | Experimental | Restarter and helper behavior for OSPFv3. | Same OSPF experimental status. |
| RFC 8665 | OSPF Segment Routing extensions | ∿ | OSPFv2 Segment Routing over the RFC 7770 Router Information LSA and the RFC 7684 Extended Prefix / Extended Link Opaque LSAs: the SR-Algorithm TLV (always Algorithm 0), one SID/Label Range TLV per SRGB range and one SR Local Block TLV per SRLB range, all area-scoped, each carrying exactly one SID/Label sub-TLV; reception enforcing Range Size greater than 0, exactly one SID/Label sub-TLV, the first-occurrence rule for a repeated SR-Algorithm or SRMS Preference TLV within one LSA, and the reserved-label hardening; SRGB index-to-label arithmetic in advertised range order; Prefix-SID, Adj-SID and LAN-Adj-SID sub-TLV codecs with reserved-bit and V/L validation; the NP/M/E outgoing-label truth table applied at the penultimate hop with the next-hop router's SRGB; algorithm-not-advertised and duplicate Prefix-SID rejection; SRLB-allocated Adj-SIDs installed and withdrawn with the adjacency. Requirements bound per line in and gated by make ze-rfc-check. rfc/short/rfc8665.md |
Fourteen MUST gaps, each annotated in. Multi-LSA capability resolution: RFC8665-3.1-4, 3.1-5, 3.4-2, 3.4-3 (no flooding-scope or Instance-ID tie-break across RI LSAs; the last LSA read wins, internal/plugins/ospf/sr_install.go, and the received SRMS preference is decoded but unused). Overlapping received ranges: RFC8665-3.2-8 (concatenated with no overlap detection, internal/plugins/ospf/sr.go). SR Mapping Server and prefix ranges: RFC8665-4-1, 4-2, 4-3, 7.1-1, 7.1-2, 7.1-3 (ze originates no IPv4 Extended Prefix Range TLV; the value encoder at internal/plugins/ospf/sr/codec.go has no ABR caller, so the IA-Flag is never set and the Range Size capacity rule is unenforced). ABR / ASBR Prefix-SID flags and inter-area propagation: RFC8665-5-8, 5-9, 7.2-1 (the IPv4 Prefix-SID builder copies the configured NP/E flags and advertises only locally configured prefixes, internal/plugins/ospf/sr.go; the equivalent rules exist only for IPv6 at internal/plugins/ospf/sr_interarea_v6.go). The feature also remains pre-production pending hardening and deployment evidence. rfc/short/rfc8665.md |
| RFC 8666 | OSPFv3 Segment Routing | ∿ | OSPFv3 Segment Routing (SR-MPLS) over RFC 8362 Extended LSAs: the OSPFv3 Extended-LSA registry type codes (Prefix-SID 4, Adj-SID 5, LAN Adj-SID 6, SID/Label 7, Extended Prefix Range 9), the MT-ID-free OSPFv3 sub-TLV layouts with V/L-implied SID width and reserved bits zeroed on send and ignored on receive, Prefix-SID origination under an Intra-Area Prefix TLV in the E-Intra-Area-Prefix-LSA, Adj-SID / LAN Adj-SID origination under a Router-Link TLV in the E-Router-LSA with withdrawal when the adjacency drops, reception from the E-Intra/Inter-Area-Prefix, E-AS-External and E-Type-7 LSAs through both the prefix-TLV and the Extended Prefix Range carriage, the §6 NP/E/M outgoing-label truth table against the next-hop router's SRGB with the IPv6 Explicit NULL label 2, unadvertised-algorithm and duplicate suppression, and §8.2 ABR inter-area Prefix-SID propagation with NP set and E clear. Requirements bound per line in. rfc/short/rfc8666.md |
Three MUST gaps, annotated in and gated by make ze-rfc-check. RFC8666-5-3 and RFC8666-5-4: reception keys Prefix-SIDs by prefix alone and never consults the carrying LSA's Instance ID (internal/plugins/ospf/sr_reception_v6.go and 163-178), so a range repeated across LSAs of one type from one originator resolves by first-seen / conflict-ignores-both rather than by smallest Instance ID. RFC8666-10-1: an invalid TOP-LEVEL TLV length drops the whole LSA (internal/plugins/ospf/v3/packet/lsa_extended.go via internal/plugins/ospf/sr_reception_v6.go), but an invalid SUB-TLV length drops only its own TLV while the remaining TLVs of the same LSA are still consumed (internal/plugins/ospf/sr_reception_v6.go). The §8.1 SR Mapping Server role and ASBR external Prefix-SID origination have no producer at all (annotated not-applicable). Same OSPF experimental status. rfc/short/rfc8666.md |
| RFC 8362 | OSPFv3 Extended LSAs | Experimental | Extended LSA framework used by OSPFv3 extensions. | Same OSPF experimental status. |
| RFC 5286 | Loop-Free Alternate FRR | Experimental | LFA and TI-LFA fast reroute: per-neighbor SPFs, loop-free / node-protecting / downstream backup selection, SR repair lists, multi-area suppression. | Two MUST gaps gated in: the LFA backup is attached to every SPF route regardless of address family, so an OSPFv3 multicast AF (RFC 5838) route inherits it (RFC5286-x-5); and no explicit Section 4.1 hold-down timer bounds how long an alternate stays active, only SPF reconvergence (RFC5286-x-6). IS-IS has no LFA. rfc/short/rfc5286.md |
| RFC 5443 | LDP IGP Synchronization (OSPF) | Experimental | OSPF LDP-IGP sync state machine (internal/plugins/ospf/ldp_sync.go): per-interface cost-out to LSInfinity (0xFFFF) while LDP is not fully operational, hold-down estimation of label-binding exchange, configured-cost restore, and whole-segment cost-out; Section 4 raises only the IP link cost (ze originates no TE LSA). Tests bound per requirement in. rfc/requirements/rfc5443.md |
One MUST gap gated in: ze has no IS-IS LDP-IGP sync, so the IS-IS 2^24-2 max-metric cost-out (RFC5443-2-3) is unimplemented. End-of-LIB (RFC5443-2-6) and the 2^24-1 misuse guard (RFC5443-2-4) are not applicable (no IS-IS sync, no End-of-LIB). rfc/short/rfc5443.md |
| RFC 5036 | LDP | Experimental | Basic Discovery, active-only TCP session FSM with Initialization/KeepAlive negotiation, label information base, downstream-unsolicited Label Mapping advertisement and reception, kernel MPLS integration. | Seven MUST gaps gated in rfc/short/rfc5036.md. Messages ze never emits: RFC5036-2.5.3-2 and RFC5036-3.5.1-1 (no Notification encoder in wire.go, so a fatal error closes the session silently), RFC5036-2.6.1.3-1 (no Label Release on a received withdraw), RFC5036-2.6.1.2-1 (SendLabelWithdraw at session.go has no production caller, so a local binding is never withdrawn on the wire). Session checks ze omits: RFC5036-2.5.1-4 (handleInit at session.go overwrites the expected peer LSR ID instead of comparing it), RFC5036-2.5.1-3 (the establishment KeepAlive at register.go is unconditional, not a response to an accepted Initialization). Forwarding: RFC5036-2.7-1 (ProgramPush at fib.go imposes labels without checking that MPLS forwarding is enabled on the interface). |
| RFC 3209 | RSVP-TE | Experimental | PATH and RESV signaling, ERO routing, bandwidth admission, SE-style make-before-break, soft-state refresh/expiry, teardown. | Two MUST gaps gated in rfc/short/rfc3209.md: RFC3209-4.3.4.1-1 -- the transit next-hop selector (engine.go) ignores the ERO L-bit and does no adjacency check, so a non-adjacent strict hop is not rejected; and RFC3209-x-1 -- the raw IP transport (transport_linux.go) sends PATH without the IP Router Alert option (same transport gap as RFC 2205). Cross-vendor interop remains constrained by available open daemons. |
| RFC 2205 | RSVP base protocol | Experimental | RSVP base common-header and object codec used by RSVP-TE: Version-1 header enforced on decode, reserved octet zeroed on send, every emitted object length a multiple of 4; tests bound per requirement in. rfc/requirements/rfc2205.md |
Three MUST gaps gated in: the receive path does not verify the RSVP checksum or drop bad-checksum messages (3.1); PATH is sent without the IP Router Alert option; and DecodeMessage does not inspect an unknown Class-Num's high-order bits, so an unknown object with high-order bits 00 is not rejected (3.10). rfc/short/rfc2205.md |
| RFC 4090 | RSVP-TE Fast Reroute | Experimental | Facility backup behavior. | One-to-one detour backup was explicitly split to later work. |
First-hop redundancy
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 9568 | VRRPv3 (IPv4 and IPv6) | ∿ | Default version. Advert encode/decode, the RFC 9568 Section 6.4 state machine (Backup/Master, Master_Down_Timer, skew time), priority and preemption (including preempt-delay), the address-owner priority 255 rule, centisecond Max_Advert_Int, virtual MAC 00:00:5e:00:01:{vrid} (IPv4) / 00:00:5e:00:02:{vrid} (IPv6) on a per-group macvlan, 224.0.0.18 / ff02::12 multicast, IP protocol 112, GTSM TTL/hop-limit 255 on TX and RX, gratuitous ARP and unsolicited NA on Master transition. | Three gaps gated in. RFC9568-6.4.3-7: Accept_Mode (Section 6.4.3) is not enforced on the dataplane -- the leaf is parsed, validated and reported, but no filtering is installed (the virtual address is installed on every promotion, internal/plugins/vrrp/instance.go), so an Active router answers traffic addressed to the virtual address whichever way it is set. RFC9568-6.4.3-4: an Active router sends no ND Router Advertisement for an IPv6 Virtual Router; the only ICMPv6 message the plugin builds is the unsolicited Neighbor Advertisement (internal/plugins/vrrp/transport/na.go). RFC9568-6.4.3-3: the solicited Neighbor Advertisement answered for a virtual IPv6 address comes from the kernel and the plugin sets no Router flag or forwarding knob for it, so the Section 6.4.3 R-bit requirement rests on host state ze does not manage. Evidence caveat: RFC9568-5.1.2.3-1 (IPv6 Hop Limit 255 on transmit) rests entirely on an integration-gated test that needs CAP_NET_RAW and CAP_NET_ADMIN (internal/plugins/vrrp/transport/transport_integration_linux_test.go, build tag integration && linux); it runs in the privileged QEMU suite and skips everywhere else. Interoperability IS proven: ze exchanges adverts with keepalived 2.3.1 under QEMU and passes election, node-death failover, and graceful-stop scenarios, including virtual-MAC ownership of the virtual IP (a foreign host resolves the VIP to 00:00:5e:00:01:{vrid}). Experimental pending deployment hardening. rfc/short/rfc9568.md |
| RFC 3768 | VRRPv2 (IPv4 only) | Experimental | Opt-in via version 2. Whole-second Advertisement_Interval encoding, v2 advert format, the v2 receive-validation ladder (version, complete-packet, checksum, VRID, Auth Type 0, interval-mismatch discard, address-list discard), the Section 6.4 state machine (priority/master election, skew time, preemption, silent losing-advert discard), virtual-MAC ownership of the VIP via a per-group macvlan, and the v2 rejection rules (no accept-mode, no IPv6). |
One gap gated in: RFC3768-6.4.3-3 -- Accept_Mode is not enforced on the dataplane (the VIP is installed on any Master transition regardless of ownership, internal/plugins/vrrp/instance.go), so a non-owner Master accepts packets addressed to the virtual IP; this is the same dataplane behavior tracked for RFC 9568. RFC 3768 authentication types are deliberately not implemented: RFC 9568 Section 9 removed them as providing no real security. Same VRRP experimental status. rfc/short/rfc3768.md |
| RFC 5798 | VRRPv3 (obsoleted by RFC 9568) | Supported | For IPv4, ze transmits the RFC 5798 pseudo-header checksum form, because that is what keepalived (proven on the wire: its own adverts use it) and the rest of the deployed base compute and require; a message-only advert is rejected by them as "Invalid VRRPv3 checksum". On receive, ze dual-accepts both this form and the RFC 9568 message-only form. | RFC 9568 Section 5.2.8 clarifies the IPv4 checksum as message-only (no pseudo-header); ze diverges from that clarification on transmit for interoperability, and counts message-only senders (checksum-rfc9568-message-only) so the strict-RFC-9568 population is visible. When that population dominates, the transmit form can be revisited. |
Access, AAA, PPP, and subscriber services
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 8414 | OAuth 2.0 Authorization Server Metadata (MCP resource server) | ∿ | As an OAuth resource server, ze fetches the AS metadata document, requires issuer and matches it against the token iss, and reads jwks_uri for verification keys. Client-only fields (authorization_endpoint, token_endpoint, response_types_supported) are not consumed. Tests bound per requirement in. internal/component/mcp/as_metadata.go, streamable_auth.gorfc/requirements/rfc8414.md |
One MUST gap gated in: ze does not force the AS metadata fetch URL to https; admits an http:// scheme, leaving transport security to the operator-configured scheme. rfc/short/rfc8414.mdinternal/component/mcp/streamable_auth.go |
| RFC 2661 | L2TPv2 | ∿ | LNS/LAC tunnel lifecycle (answerer and initiator: ze dials SCCRQ, verifies SCCRP, sends SCCCN), AVP codec, hidden-AVP MD5 codec (present but not wired into message encode/decode), challenge/response, reliable control channel, HELLO, StopCCN, data sessions, LNS-side outgoing call (OCRQ/OCRP/OCCN) via request l2tp outgoing-call, dial-target config, LAC PPPoE→L2TP relay (control plane). |
Feature remains Partial; see L2TP guide for operational limits. One MUST gap gated in rfc/short/rfc2661.md: RFC2661-4.3-1 -- the hidden-AVP MD5 cipher (internal/component/l2tp/hidden.go) is implemented and unit-tested but not wired into any message encoder/decoder, so no Random Vector precedes a hidden AVP on send and precedence is not enforced on receive. Initiator tunnel interop proven vs xl2tpd (test/l2tp-interop/scenarios/03). LAC data-plane bridge (A-4) is QEMU/CAP_NET_ADMIN-gated. |
| RFC 1661 | PPP LCP | ∿ | The full ten-state RFC 1661 Section 4.1 option-negotiation automaton, LCP packet and option codecs, Configure-Request/Ack/Nak/Reject negotiation with Reject-over-Nak-over-Ack precedence and verbatim option echo, Terminate-Request/Ack, Code-Reject, Echo keepalive with Magic-Number, two-octet Protocol framing, and the common NCP structure reused by IPCP and IPv6CP under L2TP and PPPoE. Tests bound per requirement in. rfc/requirements/rfc1661.md |
Carries the L2TP and PPPoE Partial status. Twenty-five MUST gaps gated in: (1) no send-side clamp to a negotiated peer MRU, so frame and LCP Length bounds come from the fixed 1500-octet buffer (RFC1661-2-2, 5-1, 5.6-3); (2) the frame dispatcher has no LCP-phase gate and buffers early NCP frames instead of discarding out-of-phase packets (RFC1661-3.4-1, 3.5-4, 3.7-2); (3) no Protocol-Reject sender and no suppression of a packet type on RXJ+ (RFC1661-3.6-3, 4.3-3, 5.7-1, 5.7-2); (4) Configure-Request carries a constant Identifier and ze keeps no record of its last transmitted request, so Configure-Ack/Nak/Reject Identifier and option matching are unchecked and a rejected MRU or Magic-Number option reappears (RFC1661-5.1-3, 5.2-3, 5.2-4, 5.3-7, 5.4-3, 5.4-4, 5.4-5); (5) Code-Reject echoes the offending Identifier (RFC1661-5.6-2); (6) a single-octet Protocol field is refused even with PFC negotiated (RFC1661-6.5-3), and a peer Magic-Number option whose Length is not 6 is Configure-Rejected rather than Nak'd, which Section 6.4 forbids once ze itself sends a Magic-Number, as it always does (RFC1661-6.4-1); (7) the Restart timer, Max-Terminate, Max-Configure and Max-Failure are not configurable and zrc is a no-op (RFC1661-4.6-1, 4.6-2, 4.6-3, 4.6-4, 4.4-2). rfc/short/rfc1661.md |
| RFC 1334 | PAP | ∿ | PAP authentication option through PPP auth handling. | Carries the L2TP and PPPoE Partial status. |
| RFC 1994 | PPP CHAP (MD5) | ∿ | CHAP-MD5 authenticator (LNS) and peer (PPPoE client): Challenge/Response/Success/Failure codec, per-call 16-octet random Challenge, changing Identifier, MD5(id | |
| RFC 2759 | MS-CHAPv2 | Supported within PPP and IPsec EAP | Mutual authentication in both the PPP and IPsec EAP paths; MPPE/MSK key derivation in the IPsec EAP path only. | ✕ |
| RFC 1332 | IPCP | ∿ | IPv4 address negotiation and pool integration, IPCP option codec (IP-Address type 3, RFC 1877 DNS 129/131), FSM negotiation to Opened, pppN address/route programming. | One MUST gap gated in: codes 8-11 received on IPCP are not Code-Rejected (mapped to LCP echo/protocol-reject handling); codes 12 and above are Code-Rejected. rfc/short/rfc1332.md |
| RFC 1877 | IPCP DNS options | ∿ | Primary and secondary DNS option parsing and negotiation; the Configure-Ack echoes acceptable options verbatim, the Configure-Reject echoes unsupported ones, and the IPv4 link stays usable with or without DNS. Tests bound per requirement in. rfc/requirements/rfc1877.md |
Carries the L2TP and PPPoE Partial status. One MUST gap gated in: a DNS option with a Length other than 6 is answered with a Configure-Nak (correcting the value) rather than a Configure-Reject, because Ze's reject path is keyed on unknown option TYPE, not option length. rfc/short/rfc1877.md |
| RFC 5072 | IPv6CP | ∿ | Interface-Identifier NCP: independent FSM, generation, Configure-Req/Ack/Nak/Reject, RA/DHCPv6-PD after Opened. | Gaps in rfc/short/rfc5072.md: the random interface identifier does not zero the u/l bit (4.1-9/4.1-11); no 1280 MTU floor for IPv6 sessions (2-2, minIPMTU=68); the collision Configure-Nak reuses s.peerInterfaceID rather than a fresh different non-zero identifier (4.1-4/4.1-5); no last-Nak-suggestion oscillation break (4.1-8). IPv6 address/prefix assignment is outside IPv6CP (DHCPv6-PD/SLAAC). |
| RFC 2516 | PPPoE | ∿ | Access concentrator (PADI/PADO/PADR/PADS/PADT discovery, AC-Cookie, session tables, AF_PPPOX kernel sessions) and PPPoE client/Host dialer, over the shared PPP driver. Tests bound per requirement in. rfc/requirements/rfc2516.md |
One MUST gap, gated in: RFC2516-5.2-2 -- BuildPADO (internal/component/l2tp/pppoe/discovery.go) can emit a PADO carrying only an AC-Name and no Service-Name tag when the AC has no configured service names (the documented accept-any config) and the client's PADI Service-Name is empty or absent, violating the one-or-more Service-Name MUST. rfc/short/rfc2516.md |
| RFC 2865 | RADIUS authentication | Supported for subscriber access | Access-Accept profile extraction, Filter-Id, Session-Timeout, Idle-Timeout, VSAs, pool selection. | Operator/admin login RADIUS (PAP) is a separate backend under system/authentication/radius; the profile attributes above are subscriber-access only. |
| RFC 2866 | RADIUS accounting | Supported for subscriber access | Start, Stop, and Interim-Update accounting records. | Admin/operator RADIUS accounting is not wired; the admin backend is authentication-only. |
| RFC 2869 | RADIUS extensions | Supported for subscriber access | Gigaword counters and selected accounting extensions. | Scoped to subscriber access. |
| RFC 5176 | RADIUS CoA and Disconnect Message | Supported for subscriber access | CoA/DM listener for RADIUS-initiated changes and disconnects. | Scoped to subscriber access. |
| RFC 8907 | TACACS+ | ∿ | SSH login PAP auth, ordered failover, MD5 pseudo-pad encryption, command accounting, optional authorization, single-connect mode; tests bound per requirement in. rfc/requirements/rfc8907.md |
Two MUST gaps gated in: RFC8907-4.6-1 -- no exact decrypted-body-length check, so a wrong shared secret yielding a plausibly-sized body is not cleanly rejected (ErrBadSecret is defined but unused); and RFC8907-6-1 -- authorization is decided on the response Status alone, and mandatory response arguments (=/*) are never parsed or enforced. rfc/short/rfc8907.md |
IPsec, IKE, EAP, and kernel security associations
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 7296 | IKEv2 | ∿ | Wire codec, cryptographic primitives, initiator and responder FSM, IKE_SA_INIT, IKE_AUTH, CREATE_CHILD_SA, INFORMATIONAL, rekey, DPD. Section 1.4 authenticated Delete on operator clear (graceful bounce); Section 2.4 state synchronization: the responder accepts a fresh IKE_SA_INIT in parallel with an established SA and supersedes it only once the new SA authenticates (never on the unauthenticated init); INITIAL_CONTACT emitted on the first IKE_AUTH request and honored on receipt. Section 1.4 liveness probes carry the Encrypted payload and authenticate under the negotiated keys. Section 2.4 refuses a verdict about the peer from an unauthenticated message, because an established SA is served by one handler that decrypts first. Section 2.5 accepts an IKE_AUTH whose payloads arrive in any order, on both the initiator and the responder path. Section 3.6 reads the first CERT payload as the peer certificate. It reads every later one as a chain intermediate. Ze sends its configured intermediate after the device certificate. Section 2.2 Message ID exhaustion: both counters stop at the 32-bit ceiling instead of wrapping. The SA rekeys itself in the headroom below the ceiling. The owner loop closes it at the ceiling (engine/msgid.go, engine/established.go). Section 2.3 window: Ze declares a window of one and accepts exactly one request id. It reads a peer SET_WINDOW_SIZE during IKE_AUTH, refuses a body that is not 4 octets, and reports the value as peer-window-size in show vpn ipsec sa. A peer request that crosses one of ours is accepted and answered, and a request outside the window is never acknowledged. Section 2.3 INVALID_MESSAGE_ID: an out-of-window request that authenticates draws the notification. Ze sends it as a NEW INFORMATIONAL request. That request carries the four-octet invalid Message ID. Ze never sends it as a response. Three bounds apply. Ze decrypts the request first, so an unauthenticated datagram draws nothing at all. Ze sends the notification only when its one request window is free. The notification therefore never displaces a liveness probe, a Delete or a rekey. A token bucket on the SA rate limits it. That bucket satisfies the MUST of Section 2.3 (engine/notify_invalid_msgid.go). Section 2.25: a rekey the peer answers with TEMPORARY_FAILURE waits 60 seconds before it is retried, per rekey kind. Sections 2.21.2, 2.21.3 and 2.21.4 error notifications: a request that fails on an authenticated IKE SA draws an encrypted error notify instead of silence. A refused Child SA rekey draws NO_PROPOSAL_CHOSEN, a malformed request draws INVALID_SYNTAX, and an unrecognized critical payload draws UNSUPPORTED_CRITICAL_PAYLOAD with the one-octet payload type. A datagram that matches no IKE SA draws an unprotected INVALID_IKE_SPI that copies the SPIs, Exchange Type and Message ID. That answer is rate limited. It is never sent in reply to a message marked as a response. It is never sent for an IKE_SA_INIT. An unprotected datagram at a CACHED message id draws no cached response and changes no SA state. This holds on the established path and on the mid-EAP responder path alike (engine/notify_error.go, engine/responder.go replayCachedResponse). The claim is bounded to that message id on purpose. An undecryptable datagram at a message id the responder is still expecting is a different path, and it does change SA state (engine/responder_eap.go handleResponderEAP). Section 3.10.1: an unrecognized error notify in a response fails the request it answers, and an unrecognized status notify is ignored and logged. Sections 1.2 and 2.6 corrected retry: an initiator that guesses the wrong Diffie-Hellman group retries the IKE_SA_INIT under the group the responder names, and refuses a group it never proposed, so a forged unauthenticated notify cannot steer it. The retry re-offers the whole configured suite set and re-anchors the signed IKE_SA_INIT octets. Section 2.6 COOKIE: the responder answers an inbound IKE_SA_INIT with a COOKIE challenge before it commits a half-open slot, and the initiator echoes the cookie as the first payload of a retry that changes nothing else. The cookie is an HMAC over the nonce, the source address and the initiator SPI under a rotating secret, is bounded to 1..64 octets on both the mint and the echo path, and a cookie that does not match is ignored rather than rejected. cookie-threshold sets how many half-open IKE SAs are tolerated before challenging, and defaults to 0 (challenge every initiation). Section 2.6.1: a second challenge replaces the first without failing, and the retry budget is bounded (engine/cookie.go, engine/sa_init_retry.go). Section 3.15.1 Configuration payload codec: the attribute type is 15 bits. Ze masks the Reserved bit on the read path and on the write path. A peer that sets that bit still has its INTERNAL_IP4_ADDRESS recognized (wire/payload_cp.go). Sections 2.19, 2.20 and 3.15.1: Ze builds no Configuration payload. It takes no IRAC role, which Section 4 permits. It ignores CFG_SET, which Section 3.15.1 permits. It sends no CFG_REQUEST, no CFG_REPLY and no CFG_ACK, and it gives out no version string. Section 3.4 key exchange pairing: the initiator refuses an IKE_SA_INIT or IKE-rekey response whose KE payload names a Diffie-Hellman group that no proposal of the same message specifies, and refuses a KE payload at all when no proposal specifies a group. A Transform ID of NONE names no group for both rules (wire/payload_sa.go, engine/fsm.go). Section 3.3.6: an IKE SA negotiation never selects the group NONE, and the one exchange that does select it, a Child SA rekey without PFS, ignores any KE payload the peer sends and omits one from the response (engine/rekey.go). Section 2.4: INITIAL_CONTACT is withheld by an identity that may be replicated. An EAP credential names a user rather than a device, so an EAP peer sends none; a pre-shared secret or X.509 device identity still sends it (engine/auth.go). Section 2.8: an IKE SA whose negotiated lifetime has expired is refused for use at the point every protected message is built, not merely scheduled for teardown, and hard expiry is no longer deferred by an in-flight rekey. The rekey trigger is placed a full retransmit budget before the hard time so the replacement exchange still has room (engine/sa.go, engine/auth.go, engine/established.go, engine/rekey.go). Section 4: a rekey the peer refuses with NO_ADDITIONAL_SAS is answered by deleting the old SA and creating a new one through the initial exchanges, instead of retrying an exchange that peer will never accept (engine/rekey.go, engine/inbound.go). Section 3.6 certificate count: certificate-count bounds the chain Ze sends and the chain it accepts, and it defaults to the four the section names. A peer that sends more is refused, never truncated (engine/cert_payload.go). Section 3.6 Hash and URL: hash-and-url turns on both encodings. Ze sends encoding 12 for one certificate and encoding 13 for a bundle, and it advertises HTTP_CERT_LOOKUP_SUPPORTED. It resolves a payload a peer sends through a bounded http fetch. That fetch permits the http scheme only. It caps the body at 64 KiB and times out after 5 seconds. It follows no redirect. It denies loopback, private, link-local and metadata addresses. It verifies the SHA-1 before any parser reads the bytes. The leaf defaults to false, so no fetch is reachable until an operator asks for one (engine/certurl.go, engine/certbundle.go). Section 2.9 traffic-selector narrowing: the responder narrows the initiator's proposed TSi/TSr to a subset its configured traffic-selector list allows, leads that subset with the initiator's first choices, and answers TS_UNACCEPTABLE when nothing is acceptable. A peer with no configured selectors accepts whatever it is offered, which is the behavior of every configuration written before the list existed. The selectors Ze puts on the wire are the selectors it programs: a proposal it cannot program exactly is narrowed further, never rounded outward (engine/ts_narrow.go, ipsec/traffic_selector.go). Section 2.9.2: a rekey is never narrowed below the scope currently in use (engine/rekey.go). Sections 1.3.1 and 2.23.1 transport mode: mode transport sends USE_TRANSPORT_MODE with the Child SA request and pins TSi and TSr to the IKE SA's own address pair, one address each. A responder accepts the request only when its own configuration asks for transport mode, and echoes the notification when it does. A peer that declines leaves a tunnel-mode Child SA, and transport-required makes that decline delete the SA instead (engine/transport_mode.go, engine/child.go). Section 3.13.1 port selectors: a selector carries all ports (0/65535) or one port under a protocol that defines ports (ipsec/traffic_selector.go). Section 4 conformance set: Ze accepts PKIX certificates signed by RSA keys of 1024 and 2048 bits. The identity passed can be ID_FQDN, ID_RFC822_ADDR or ID_DER_ASN1_DN, which binds against the certificate subject exactly. remote-id-type key-id lets an operator accept ID_KEY_ID, which corresponds to no certificate field. The same leaf pins one identity type per peer (engine/remote_id.go). Section 3.3 transform alternatives: one proposal CAN carry several transforms of the same Transform Type. Ze reads every one of them as an alternative, and it does not keep only the last. A peer CAN offer two Diffie-Hellman groups, two key lengths, two PRFs or two integrity algorithms in one proposal. Ze considers all of them, in the order the peer listed them. Ze selects the peer's first choice that it supports. The number of combinations one proposal expands to is bounded, so an unauthenticated peer cannot turn a cross product into unbounded work (engine/initiator.go). Section 1.4.1 Delete: an inbound Child SA Delete is resolved to the pair the peer named by SPI and that pair is closed, and the response carries a Delete payload naming the paired SA going in the other direction. A Delete that crosses one Ze already sent for the same pair is answered without a Delete payload, and the two halves of the pair go at the two points the section names. An IKE SA Delete still draws an empty response (engine/delete.go). Section 1.5: the out-of-SA notification emitter is a fixed point, so its own output fed back to it produces nothing and two nodes cannot trade messages forever (engine/notify_error.go). Section 2.12: closing an IKE SA erases SK_d and the rest of SK_*, the Diffie-Hellman private value and the EAP MSK, and releases the nonces, on every path that ends an SA including an abandoned half-open handshake (engine/sa.go, engine/established.go, engine/fsm.go). Section 2.16: the EAP shared key generates the AUTH payloads that follow EAP Success, and the responder sends Success on a completed method and Failure on a refused one (eap/eap.go, engine/eap_auth.go). Section 2.24 ECN: no config leaf and no negotiated parameter can reach an ECN knob, and the netlink state Ze fills carries no flags field, so XFRM_STATE_NOECN is unreachable and the kernel's ECN propagation stands (dataplane/xfrm_linux.go). Section 3.1: a liveness probe sets the I bit from the ORIGINAL initiator role rather than hardcoding it (engine/dpd.go). Section 3.5: an ID_FQDN or ID_RFC822_ADDR carrying a terminator octet is refused, on the receive path against a peer's asserted identity and at commit against local-id and remote-id (engine/remote_id.go, ipsec/validate.go). Section 3.3 ESP alternatives: every transform of a type in one ESP proposal is read as an alternative, and a key length is only ever compared beside the id of the transform that carried it (engine/responder.go). Section 2.23 NAT-T: one Child SA receives BOTH ESP forms, and the form Ze SENDS follows the NAT verdict alone. Reception and transmission are two decisions. The inbound XFRM state carries the encapsulation template when a NAT was detected or the IKE SA runs on port 4500, so the kernel serves that form on its fast path. The other form is served beside the kernel: a raw IPPROTO_ESP reader takes the bare datagram XFRM refused and re-presents it through port 4500, which carries UDP_ENCAP, so the kernel hands XFRM the encapsulation type the template wants. Ze encapsulates on transmission only when it detected a NAT, which RFC 7296 Section 2.23 makes mandatory there and leaves free otherwise. Reading the port as a transmission signal broke interop against a strongSwan that floats to port 4500 for MOBIKE with no NAT present and then sends bare ESP (engine/child.go, dataplane/espform.go, dataplane/espform_linux.go, transport/encap_linux.go). |
✕ |
| RFC 4301 | IPsec architecture | Supported | Native control-plane SPD/SAD model projected to kernel XFRM (IKEv2 tunnel-mode + RFC 4552 manual transport-mode); per-packet SPD/SAD processing is kernel-delegated. | One MUST gap (RFC4301-4.4.1.1-1): SPD selectors are limited to IP-prefix + next-layer-protocol; TCP/UDP/SCTP port and ICMP type/code selectors are not modeled or projected (internal/component/ike/dataplane/dataplane.go; engine/initiator.go tsToIPNet drops ports). |
| RFC 4302 | Authentication Header | Supported in OSPFv3 manual IPsec path | AH algorithm planning and RFC 4552 OSPFv3 use. | Scoped to configured manual IPsec support. |
| RFC 4303 | ESP | Supported | ESP SA parameter model, protocol 50, tunnel and transport modes, XFRM installation, OSPFv3 manual ESP. | ✕ |
| RFC 3948 | UDP encapsulation of ESP | Supported | NAT-T non-ESP marker, UDP 4500 encapsulation, NAT keepalive, XFRM UDP encap attributes. | ✕ |
| RFC 4555 | MOBIKE | Unsupported | None; the IKE engine does not define the MOBIKE notify types (16396/16400) and does not handle UPDATE_SA_ADDRESSES. | Responder role (announce MOBIKE_SUPPORTED, accept UPDATE_SA_ADDRESSES, migrate XFRM endpoints) is not implemented. |
| RFC 3748 | EAP | Supported in IPsec | EAP framework inside IKEv2 IKE_AUTH, Success and Failure handling. | ✕ |
| RFC 2759 | MS-CHAPv2 (EAP inside IKEv2) | ∿ | NtPasswordHash (UTF-16LE + MD4), ChallengeHash with DOMAIN-prefix stripping, ChallengeResponse (DES), GenerateAuthenticatorResponse, and authenticator-side Response validation (Value-Size=49, zero Reserved/Flags, uppercase S=); EAP authenticator and peer roles both implemented (internal/component/ike/eap). | Three MUST gaps: x-6 no MS-CHAPv2 Failure (Code 4) packet is emitted (EAP-Failure used instead), so the C= fresh-challenge field is never produced; x-7 the peer only hex-decodes the S= field and never recomputes the expected Authenticator Response to compare or disconnect on mismatch; x-12 an NT-Response mismatch terminates via EAP-Failure but does not carry the mandated E=691 error code. |
| RFC 5216 | EAP-TLS (EAP inside IKEv2) | ∿ | Full EAP-TLS handshake as both authenticator (RequireAndVerifyClientCert + ClientCAs) and peer (RootCAs), L/M/S fragmentation with a 64KB reassembly bound and empty-message fragment ACK, and MSK export via the label "client EAP encryption" feeding the IKEv2 AUTH payload; certs from the PKI store. Section 2.1.3 termination in both directions: a rejected peer receives the fatal TLS alert in an EAP-Request, the authenticator waits for its EAP-Response and only then sends EAP-Failure, and a peer TLS alert that rejects the authenticator is answered with EAP-Failure in the round that carries it. A peer that rejects the authenticator replies first and reports the cause on the round after. Section 2.1.3 termination on the success path: the authenticator sends its change_cipher_spec and Finished closing flight, the peer answers with a no-data EAP-Response, and the authenticator answers that with EAP-Success. | Three MUST gaps in rfc/short/rfc5216.md: RFC5216-5.4-1/5.4-2 no CRL/OCSP/post-auth revocation checking (crypto/tls performs none, no VerifyPeerCertificate callback); RFC5216-2.4-5 the mandatory TLS_RSA_WITH_3DES_EDE_CBC_SHA ciphersuite is not offered (TLS 1.2+ Go defaults exclude insecure 3DES). |
| RFC 5282 | IKEv2 AEAD algorithms | Supported | AES-GCM IKEv2 AEAD encryption and decryption framing. | ✕ |
DNS, provisioning, MRT, and flow telemetry
| RFC | Area | Status | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|
| RFC 1035 | DNS message behavior | ∿ | DNS cache TTL handling, plus authoritative GeoDNS and AS112 response shaping. The reply carries the AA bit and the compression-off, recursion-unavailable shape, re-asserted after the answer function on every query. A name outside every served zone draws NXDOMAIN, and a name inside one with no matching record draws NODATA plus the zone SOA. Zone and label matching folds case. TTLs are bounded to 0. 2147483647. The AS112 UDP and TCP listeners bind port 53. Obligations extracted 2026-07-30 and bound per line in. The walk of all 73 sections is recorded in. internal/core/dnsserver/handler.gorfc/short/rfc1035.mdrfc/extraction/rfc1035.json |
Not enrolled. RFC 1035 predates RFC 2119 and states every obligation in lowercase indicative prose. The extraction read that prose as normative, and found several obligations with no code path at all. The 512-octet UDP bound is never enforced and the TC bit is never set: writes whatever the answer function produced. A response TTL is never raised to the zone SOA MINIMUM. An unsupported inverse query draws no Not Implemented reply. Ze performs no zone transfer of any kind. The DNS wire codec is, so several encoding obligations admit no negative test through Ze at all. Declared backlog in while an owner ruling is outstanding. internal/core/dnsserver/handler.gointernal/plugins/geodns/server.gogithub.com/miekg/dnsrfc/not-enrolled.txt |
| RFC 2181 | DNS clarifications | ∿ | Authoritative GeoDNS/AS112 answers with RRSet-consistent per-record TTLs, the section 8 0..2147483647 TTL bound (internal/plugins/geodns/config.go), canonical NS targets with A glue, UDP reply source-address/port fidelity, wire label/name limits, and a stub-resolver cache that replaces whole RRSets without merging; tests bound per requirement in rfc/short/rfc2181.md. | One MUST gap (RFC2181-5.1-1): GeoDNS and AS112 never set the TC bit or call miekg Truncate, so an oversized RRSet would be sent unmarked rather than truncated. The recursive-resolver data-ranking, DNSSEC/SIG, AXFR, and CNAME/PTR-authoring MUSTs are not-applicable (ze is authoritative plus a stub resolver only). |
| RFC 8484 | DNS Queries over HTTPS (DoH) | ∿ | DoH server on the shared DNS harness: application/dns-message over TLS, POST and GET (base64url-unpadded ?dns=), the /dns-query path, Cache-Control max-age capped at the smallest Answer-section TTL, and 405/415/400 on bad method/media/body. Server role only -- ze is not a DoH client. | One MUST gap (RFC8484-5.1-2): for a NODATA response minAnswerTTL reads only the Answer section, so the HTTP freshness lifetime is not capped at the Authority SOA MINIMUM field. Six client-role and media-type-definer MUSTs are not-applicable (ze plays no DoH client and defines no media type beyond application/dns-message). |
| RFC 7858 | DNS over TLS (DoT) | ∿ | DoT server on the shared DNS harness: a TLS-wrapped TCP listener (miekg/dns RFC 7766 two-octet length-prefixed framing) on the DoT port, default 853, sharing the same dns.Handler as cleartext and DoH; TLS 1.2 minimum (BCP 195); cleartext queries to the DoT port are refused. Server role only -- ze is not a DoT client. | One MUST NOT gap (RFC7858-3.1-3): ze does not reject a DoT listen-port of 53 (internal/core/dnsserver/secure.go validates only 1..65535). The DoT client-role MUSTs (connect, response-matching, key-pinning, bootstrap alerting) and the TCP-Fast-Open MUSTs are not-applicable -- ze plays no DoT client and its DoT listener uses no TCP Fast Open. |
| RFC 7871 | EDNS0 Client Subnet | ∿ | GeoDNS consumes the EDNS0 client-subnet ADDRESS (a MAY, sections 7.2.1/11.1) to select a tailored answer by longest-prefix host-set match, or the packet source, per the client-ip-source mode (internal/core/dnsserver/client.go); it emits no ECS option in any query or response and caches nothing by network; tests bound per requirement in. rfc/short/rfc7871.md |
Six MUST gaps, each annotated in: GeoDNS uses ECS to tailor answers but includes no ECS option in its responses, so it neither echoes the option, indicates support, nor sets SCOPE (RFC7871-7.2.1-5, 7.2.2-2, 12.1-4, 12.1-5), and it validates no consumed option's FAMILY nor returns FORMERR for a malformed one (RFC7871-7.2.1-3, 7.2.1-4). The resolver/forwarder origination, ECS-response validation, network-scope caching, and DNSSEC-tailoring MUSTs are not-applicable: ze originates, forwards, and caches no ECS and is a plain stub resolver. rfc/short/rfc7871.md |
| RFC 4035 | DNSSEC protocol modifications | ∿ | Security-aware non-validating stub resolver: dnssec-validation permissive/strict sets the EDNS0 DO bit with CD clear and a 4096-octet advertised buffer (internal/component/resolve/dns/resolver.go), rejects (strict) or logs (permissive) an upstream SERVFAIL, accepts an AD-clear NOERROR answer from an unsigned zone, disregards the AD bit of a response, and returns ordinary records from answers that also carry RRSIG/NSEC/DNSKEY. On the authoritative side the shared harness copies the query's CD bit into the reply, ignores the query's AD bit, never asserts AD for local zone data, performs no DNSSEC additional processing, and answers a DS query inside a served zone as authoritative no-data; tests bound per requirement in. rfc/short/rfc4035.md |
Three MUST gaps, each annotated in: the name server supports no EDNS0 message size extension -- it emits no OPT pseudo-RR and honors no requestor payload size (RFC4035-3-1) -- and its UDP listener reads at most 512 octets because dns.Server.UDPSize stays zero (RFC4035-3-2, internal/core/dnsserver/manager.go); and the stub rests its strict-mode decision on the upstream's validation carried over unauthenticated plain UDP (RFC4035-4.9.3-2). The zone-signing, signed-response, zone-transfer, recursive-server, and local-validation MUSTs are not-applicable: ze signs no zone, holds no DNSKEY/RRSIG/NSEC/DS record, never recurses, and runs no validator or BAD cache. rfc/short/rfc4035.md |
| RFC 7534 | AS112 operations | Supported | Authoritative sink for misdirected RFC 1918 and link-local reverse DNS. | ✕ |
| RFC 7535 | EMPTY.AS112.ARPA | Supported | EMPTY.AS112.ARPA DNAME-redirection zone. | ✕ |
| RFC 2131 | DHCPv4 | ∿ | Both roles. Server: DORA, leases, static mappings, PXE support -- every OFFER/ACK/NAK carries the server identifier, OFFER and ACK carry the lease time and ordered T1/T2 timers, no client-only option (50, 55, 57, 61) is ever echoed back, a NAK carries nothing beyond the message type and server identifier, the magic cookie is required on input and emitted on output, and options are read and written strictly inside the options field. Client: the iface-dhcp plugin runs a long-lived DHCPv4 client per interface unit -- ze owns the lease state machine (T1/T2 arithmetic, renewal, expiry teardown, address and default-route installation) and authors options 12 and 61, while DORA and RENEWING message construction belong to the vendored nclient4 library. Tests bound per requirement in. internal/plugins/iface/dhcp/rfc/short/rfc2131.md |
Eighteen MUST gaps, each annotated in. Server: DHCPNAK delivery (RFC2131-4.3.2-1 unicasts to a non-zero ciaddr instead of broadcasting, RFC2131-4.3.2-2 never forces the broadcast bit behind a relay); INIT-REBOOT for an unknown client draws an ACK rather than silence (RFC2131-4.3.2-3); a declined address is handed back to the client that declined it (RFC2131-4.3.3-1); only one address per subnet is accepted as the server identifier (RFC2131-4.1-1); with no default-router configured the server identifier falls back to a pool address or the subnet network address, neither of which the server answers on (RFC2131-4.1-2); the client identifier option 61 is never read, so every client is keyed by chaddr (RFC2131-4.2-1); the Parameter Request List is never parsed, so the section 4.3.1 selection rules govern no code path (RFC2131-4.3.1-1, 4.3.1-3, 4.3.1-5); and the vendor class is matched by "PXEClient:" prefix rather than exactly (RFC2131-4.3.1-7). Client: option 61 is sent at acquisition and omitted on every renewal (RFC2131-2-2); the configured client-id is emitted with no uniqueness check (RFC2131-2-1); an address found in use is kept rather than declined, and no DHCPDECLINE path exists (RFC2131-3.1-7); retransmission doubles the timeout with no randomization (RFC2131-4.1-8); the renewal is broadcast rather than unicast to the server identifier (RFC2131-4.1-10); the lease default route survives around 70 seconds past expiry because blocking renewal attempts add to a fixed sleep budget (RFC2131-4.4.5-1); and a renewal ACK carrying a different yiaddr leaves the previous address installed (RFC2131-4.4.5-5). DHCPINFORM is answered by no code path and sent by none, option overload (52) is neither emitted nor honored, and the remaining client-role MUSTs are not-applicable because ze produces none of the governed bytes: the vendored nclient4 library constructs them, or ze never enters the state (INIT-REBOOT, DECLINE, RELEASE, INFORM). rfc/short/rfc2131.md |
| RFC 2132 | DHCP options | ∿ | DHCP server encodes options 1, 3, 6, 15, 51, 53, 54, 58, 59 and PXE options 43/60/66/67; it parses received message type (53), requested IP (50), server identifier (54), and PXE class/arch (60/77/93). Tests bound per requirement in. rfc/requirements/rfc2132.md |
One MUST gap, tracked in: RFC2132-9.8-1 -- the server emits a fixed option set in a fixed order (buildReply internal/plugins/dhcpserver/handler.go) and never reads the client Parameter Request List (option 55), so it does not honor the client-requested option order. rfc/short/rfc2132.md |
| RFC 4578 | DHCP PXE options | Supported | PXE boot option injection for BIOS and UEFI bootfile selection. | ✕ |
| RFC 1350 | TFTP | Supported | Read-only TFTP server for PXE bootloader delivery. | RFC1350-2-3 unmet (Sorcerer's Apprentice fix): sendAndWaitACK retransmits DATA on any non-matching ACK (handler.go) instead of silently ignoring a duplicate or stale ACK. |
| RFC 2347 | TFTP option negotiation | Supported | Option negotiation for TFTP provisioning paths. | ✕ |
| RFC 6396 | MRT | Supported | Daemon-side MRT recording, TABLE_DUMP_V2 snapshots, BGP4MP messages, analysis tools. | One MUST gap gated in rfc/short/rfc6396.md [RFC6396-4.4.3-1]: the live BGP4MP writer always emits the BGP4MP_MESSAGE_AS4 subtype and records the on-wire message verbatim without checking the session's negotiated 4-byte-AS capability, so a message from an OLD (2-byte) peer carries a 2-byte AS_PATH mislabeled as AS4. RIB-path AS_PATH is unaffected (canonicalized to 4-byte). |
| RFC 8050 | MRT ADD-PATH | ∿ | Add-path MRT subtypes: TABLE_DUMP_V2 RIB_*_ADDPATH (8-11) carry a 4-byte big-endian Path Identifier between Originated Time and Attribute Length; RIB_GENERIC_ADDPATH (12) keeps the Path Identifier in the raw NLRI blob and does not redefine the RIB Entry; BGP4MP add-path subtypes (8-11) preserve the Path Identifier inside the encapsulated message's NLRI; add-path is distinguished purely by MRT subtype. Tests bound per requirement in. rfc/requirements/rfc8050.md |
One MUST gap, gated in: ze selects the MRT add-path subtype from a static operator config toggle (ribSubtype/bgp4mpTypeSubtype, config.go AddPath) rather than from each peer's negotiated RFC 7911 Add-Path capability (OnBGPMessage does not consult it), so a single dump cannot represent a mix of add-path and non-add-path peers. rfc/short/rfc8050.mdinternal/plugins/mrt/dump.gointernal/plugins/mrt/component.go |
| RFC 3954 | NetFlow v9 | Experimental | Flow export templates and records over UDP (exporter): no Data FlowSet before its Template, cumulative per-observation-domain sequence numbers, network byte order, constant Template IDs; tests bound per requirement in. rfc/requirements/rfc3954.md |
Template refresh is time-interval-based only, with no packet-count-based refresh interval (RFC3954-x-2 gap). ze is an exporter only; the v9 collector requirements are not applicable. |
| RFC 7011 | IPFIX | Experimental | IPFIX export templates and records over UDP (exporter): version 0x000a, Template IDs >= 256, IANA (E=0) fixed-length field specifiers, zeroed Set padding, and periodic UDP Template refresh at a configurable interval; tests bound per requirement in. rfc/requirements/rfc7011.md |
One MUST gap in [RFC7011-10-1]: the exporter transmits over UDP only and implements no SCTP transport, so the mandatory SCTP support is absent. ze is an exporter only; IPFIX Collecting Process requirements are not applicable. rfc/short/rfc7011.md |
Drafts and non-RFC standards tracked near RFC work
These are not RFCs, but they sit next to RFC implementation status and are useful when reading the tables above.
| Standard | Area | Status | Note |
|---|---|---|---|
| draft-abraitis-idr-addpath-paths-limit | BGP PATHS-LIMIT | Supported | Per-family path-count limit capability for ADD-PATH. |
| draft-ietf-sidrops-aspa-verification | ASPA path verification | ∿ | Section 6 verification algorithm (upstream/downstream), AS_SET to Unknown, prepend collapse, AS0-in-provider rejection, RTR ASPA PDU (Type 11) consumption, and re-validation on cache change. Two MUSTs unmet: per-AFI ASPA records (6-4, the AFI flag is parsed then discarded and the cache is keyed by customer AS alone, so per-AFI records overwrite each other); Invalid-not-preferred (8-1, ASPA state drives only reject/keep with default LogOnly, so an accepted Invalid route can outrank a Valid one for the same prefix). |
| draft-ietf-idr-linklocal-capability | Link-local next-hop capability code 77 | Supported | Capability declaration around RFC 2545 next-hop behavior. |
| draft-ietf-idr-software-version | BGP Software Version capability code 75 | Supported | Software version advertisement plugin. |
| draft-ietf-bess-mup-safi | BGP Mobile User Plane SAFI 85 | ∿ | The BGP-MUP NLRI codec only: ipv4/mup and ipv6/mup family registration, ISD/DSD/T1ST/T2ST encoding from config and route commands, header plus RD decoding, the RFC 7606 Section 5.4 ruling that discards a route whose Architecture Type is not 1 or whose Route Type is outside 1. 4 at ingress (internal/component/bgp/plugins/nlri/mup/rfc7606.go, RecognizeNLRI), MUP extended-community config syntax, and family-generic MP_REACH announcement (internal/component/bgp/plugins/nlri/mup). Thirty-seven MUST gaps are annotated per line in. Withdrawal: no MUP NLRI reaches the family-generic MP_UNREACH encoder (internal/component/bgp/reactor/peer_rib_routes.go) -- its callers read the PeerOpWithdraw queue, and neither withdrawal entry point parses SAFI 85 (internal/component/bgp/plugins/cmd/update/update_text_nlri.go, internal/component/bgp/plugins/cmd/announce/announce.go) -- so a Type 1 ST or Type 2 ST withdrawal cannot be emitted (3.3.8-1, 3.3.11-1). Receive side: nlrisplit registers SplitMUP for SAFI 85 since 2026-08-04 (internal/core/bgp/nlri/nlrisplit/register.go), so a received MUP route is stored as an opaque Adj-RIB-In entry (insertPoolNLRIs) and a withdrawal deletes exactly the NLRI it names (removePoolNLRIs, internal/component/bgp/plugins/rib/rib.go). Four routing-instance obligations stay open because ze models no MUP routing instance and no route-type-aware wildcard delete exists (DRAFT-IETF-BESS-MUP-SAFI-3.3.3-2, 3.3.6-2, 3.3.9-1, 3.3.9-2). ParseMUP keeps the route-type body opaque (internal/component/bgp/plugins/nlri/mup/types.go), so no RFC 7606 treat-as-withdraw fires on an out-of-range prefix length, wrong-size address, zero TEID, invalid endpoint or source length, over-long T2ST endpoint length, or non-3gpp-5g architecture type (3.1.1-1, 3.1.1-2, 3.1.2-1, 3.1.3-1, 3.1.3.1-1, 3.1.3.1-2, 3.1.3.1-3, 3.1.3.1-4, 3.1.4-1, 3.1.4.1-1, 3.1.4.1-2), nor on a missing Prefix-SID, a nexthop/locator mismatch, or a Type 2 ST route without the BGP MUP Extended Community (3.3.3-1, 3.3.3-3, 3.3.3-4, 3.3.6-1, 3.3.12-1). Send side: ze runs no MUP PE or MUP Controller function, so route targets, the BGP MUP Extended Community, the Prefix-SID, the GTP4.E/GTP6.E function, the required T1ST TEID and Endpoint Address, and the PE or controller IPv6 nexthop are whatever the operator configures rather than derived (3.3.1-1, 3.3.1-2, 3.3.1-3, 3.3.1-4, 3.3.2-1, 3.3.4-1, 3.3.4-2, 3.3.4-3, 3.3.4-4, 3.3.5-1, 3.3.5-2, 3.3.7-1, 3.3.7-3, 3.3.10-1, 3.3.10-2). rfc/short/draft-ietf-bess-mup-safi.md |
| ISO/IEC 10589 | IS-IS base protocol | Experimental | Base IS-IS protocol reference, paired with the IS-IS RFC rows above. |
| sflow-v5 | sFlow export | Experimental | Flow export protocol alongside NetFlow v9 and IPFIX. Three MUST gaps in rfc/short/sflow-v5.md: datagram-level sequence numbers split across two independent counters per sub-agent (SFLOW-V5-x-9); no expanded sample types, so ifIndex > 2^24-1 is truncated by the 24-bit source_id mask (SFLOW-V5-x-12); unavailable if_counters fields are exported as 0 instead of the max-value unavailable sentinel (SFLOW-V5-x-16). |