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.
Every row below is declared by the RFC's own summary, in the ## Meta table of rfc/short/<stem>.md. A row belongs here only when current docs, source code, tests, or learned closure notes tie the RFC to a Ze feature, and a summary that ties to none writes | Support | - | instead.
This page is the product view. The per-requirement detail behind it is published at /quality/rfc-compliance/<stem>/, one page for every summary under rfc/short/, naming each requirement, the tests bound to it, the audit verdict a reader recorded, and every gap and untested MUST under its own requirement id. The 40 of 200 summaries (20.0%) with no row on this page are published there too, and the index at /quality/rfc-compliance/ links all of them.
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 ./le verify current mode full 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. ./le rfc check enforces exactly three properties of the tables below.
- Gap-count agreement. A
Support remainingcell can spell a number immediately before MUST or SHALL. That number must equal the count of{gap}annotations in the same 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. - A support claim rests on something. Two rows are refused. A row claiming anything but
UnsupportedorFutureover a summary that declares no MUST-level requirement, unless that summary declares itself non-normative or carries a manual-walk extraction sign-off. And a row PROMISING conformance --Supported, alone or with a scope after it -- whoseProofcell says none of its gated MUST-level requirements is proven. A checklist nothing passes carries a support promise no better than an empty one, and the two escapes above do not reach it: each says the DOCUMENT imposes no MUST, which is a different question from whether Ze meets one.Partialis the honest row there, and it is not refused.
Row presence needs no check any more. A row exists exactly where a summary declares a section, so an enrolled RFC with no row, a row naming an RFC with no summary, and two rows for one RFC are each unrepresentable rather than refused.
Proof is the one derived cell in these tables. It restates that RFC's own coverage from rfc/requirements/<stem>.md: how many MUST-level requirements the summary gates, then how many of them carry a test in BOTH polarities (proven), how many carry an annotation instead (annotated), and how many carry neither -- one polarity only, or no test at all (untested). The three sum to the gated count. A summary gating no MUST-level requirement reads no gated MUST, and no cell an author writes changes any of it.
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.
One coverage limit is 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.
BGP base protocol, capabilities, and session safety
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 4271 | BGP-4 base protocol | ∿ | 101 gated: 76 proven, 25 annotated, 0 untested | 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 |
Fifteen 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). 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 | 4 gated: 3 proven, 1 annotated, 0 untested | 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 | ∿ | 9 gated: 2 proven, 7 annotated, 0 untested | 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 | 9 gated: 7 proven, 2 annotated, 0 untested | Capability TLV parser, encoder, unknown-capability ignore behavior, negotiated session view. | ✕ |
| RFC 4760 | Multiprotocol BGP | Supported | 6 gated: 4 proven, 2 annotated, 0 untested | 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 | ∿ | 30 gated: 28 proven, 2 annotated, 0 untested | 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), the whole receive-side procedure of Section 4.2.3 (the AGGREGATOR versus AS4_AGGREGATOR choice, the AS-number-count comparison, the leading-segment prepend and its confederation adjacency rule), the Section 4.1 and Section 6 discard of an AS4_PATH or AS4_AGGREGATOR received from a NEW speaker, AS4_PATH/AS4_AGGREGATOR malformed-attribute validation, AS_TRANS in the OPEN My AS field; tests bound per requirement in. rfc/requirements/rfc6793.md |
One MUST-level gap, 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). rfc/short/rfc6793.md |
| RFC 2918 | Route Refresh | Supported | 6 gated: 6 proven, 0 annotated, 0 untested | Route Refresh capability and ROUTE-REFRESH message handling. | ✕ |
| RFC 7313 | Enhanced Route Refresh | Supported | 10 gated: 3 proven, 7 annotated, 0 untested | 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 | 9 gated: 8 proven, 1 annotated, 0 untested | 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 | 12 gated: 8 proven, 4 annotated, 0 untested | Extended message capability and 65535-byte message limit when negotiated. | ✕ |
| RFC 8950 | Extended Next Hop | Supported | 6 gated: 3 proven, 3 annotated, 0 untested | IPv6 next-hop for IPv4 NLRI and negotiated extended next-hop lookup. | ✕ |
| RFC 5549 | Legacy extended next-hop encoding | Supported | 6 gated: 3 proven, 3 annotated, 0 untested | Backward-compatible parser for the older format superseded by RFC 8950. | Main public claim uses RFC 8950. |
| RFC 4724 | Graceful Restart | ∿ | 26 gated: 16 proven, 10 annotated, 0 untested | 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). The send covers a session where neither speaker advertised a Multiprotocol capability. RFC 4271 carries that session as IPv4 unicast, so RFC 4724 Section 4 owes it a marker. Negotiate (internal/core/bgp/capability/negotiated.go) reads a side that advertised none as advertising ipv4/unicast. sendInitialRoutes (internal/component/bgp/reactor/peer_initial_sync.go) then sends the marker. FRR 10.3.1 decodes it in test/interop/scenarios/no-family-peer-eor-frr. The marker also waits for the plugins that push routes into the session: setState marks it owed, sendInitialRoutes closes the queueing gate and only then waits for every binding ProcessBinding.MayPushRoutes counts, by the send [ update ] rail or the send [ raw ] one (internal/component/bgp/reactor/peer_initial_sync.go, peer_settings.go). test/plugin/initial-sync-barrier-raw.ci asserts the injected route and the marker byte for byte, in that order, and goes red when the SendRaw arm of that predicate is removed. 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 | ∿ | 25 gated: 18 proven, 7 annotated, 0 untested | 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 | 19 gated: 13 proven, 6 annotated, 0 untested | 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 2545 | IPv6 next-hop handling | Supported | 4 gated: 4 proven, 0 annotated, 0 untested | 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 | 1 gated: 1 proven, 0 annotated, 0 untested | 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 | ∿ | 3 gated: 0 proven, 3 annotated, 0 untested | 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 | Codification of AS 0 processing | Supported | 5 gated: 5 proven, 0 annotated, 0 untested | (AS4_PATH and AS4_AGGREGATOR validators), (validateASPath, validateAggregatorAttr), (validateOpenPeerAS, both OPEN rails), (sendOpen). internal/component/bgp/message/rfc7607.gointernal/component/bgp/message/rfc7606.gointernal/component/bgp/reactor/session_open_as.gointernal/component/bgp/reactor/session_negotiate.go |
All 5 MUST-level requirements implemented and extracted, each with a positive and a negative test tag. AS 0 in AS_PATH is treat-as-withdrawn and in AGGREGATOR, AS4_PATH and AS4_AGGREGATOR it is discarded, which is what RFC 7606 sections 7.2 and 7.7 and RFC 6793 section 6 each prescribe. An OPEN whose peer AS is zero, in either the two-octet My AS field or the Four-octet AS capability, draws NOTIFICATION 2/2 Bad Peer AS on both OPEN rails. This row read Unsupported from 2026-08-30 until the implementation landed the same day; before that it claimed Supported on a description belonging to RFC 4486. |
| RFC 9687 | Send Hold Timer | Supported | 13 gated: 13 proven, 0 annotated, 0 untested | Send Hold Timer, auto duration max(8min, 2x hold-time), NOTIFICATION code 8 on expiry. |
✕ |
| RFC 7606 | Revised UPDATE error handling | ∿ | 52 gated: 45 proven, 7 annotated, 0 untested | 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 ./le 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 | 5 gated: 4 proven, 1 annotated, 0 untested | 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 | 3 gated: 2 proven, 1 annotated, 0 untested | 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. On the client side of the same document, Section 2.2.2.2 (RFC7947-2.2.2.2-1) asks that a leftmost-AS check be possible to disable; ze runs no ingress leftmost-AS check at all, so a route ze receives from a route server whose leftmost AS_PATH AS is another client's is accepted in every configuration (LoopIngress). The walk that bounds this row against the RFC's own text is. internal/component/bgp/reactor/filter/loop.gorfc/extraction/rfc7947.json |
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. Section 2.1 requires a route server to accept every UPDATE it receives from a client for inclusion in its Adj-RIB-In, and ze does not: notifyMessageReceiver returns at the ordered ingress step loop's non-accept branch, ahead of the cache add, ahead of reactorForwardRS and ahead of the plugin dispatch, so an UPDATE a filter denied is absent from show bgp adj-rib-in. LoopIngress denies with no operator configuration, so the path is reachable by default. Thomas ruled on 2026-08-30 that ze stores such an UPDATE first and marks the entry filtered; that work is homed in, which reserves the id RFC7947-2.1-1, and site 2.1:1 of the extraction sign-off records the relocation. internal/component/bgp/reactor/reactor_notify.goplan/immediate/spec-rfc7947-adj-rib-in-accepts-filtered-updates.md |
| RFC 9003 | Administrative Shutdown Communication update | Supported | 4 gated: 4 proven, 0 annotated, 0 untested | 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 Linux; FreeBSD needs a setkey(8) SAD entry |
9 gated: 6 proven, 3 annotated, 0 untested | Per-peer TCP MD5 through connection { md5 { password; ip; } }. parsePeer (internal/component/bgp/reactor/config.go) reads the key, NewSession puts it on the dialing socket and md5PeersForListener puts it in the listening socket's key set (internal/component/bgp/reactor/session.go, reactor.go), and setTCPMD5Sig installs it with TCP_MD5SIG before connect and before bind (internal/core/network/md5_linux.go), so the kernel signs and validates every segment of the peering. Ze computes no digest of its own; conformance is judged on what the whole stack produces, and every requirement row is proven at the boundary ze owns. The nine gated rows of each carry both polarities or a single-polarity annotation: a loopback session under one shared key carries 256 KiB, a mismatched key is dropped with no answer at all, and a key held by one end only carries no session (internal/core/network/md5_rfc2385_linux_test.go). An FRR peer configured with the same password establishes with ze in the nightly interop lab, where the scenario's assertion is FRR's own view of the session (test/interop/scenarios/bgp-md5-auth-frr, scenarioOperations in internal/le/interoplab/bgp/checkers.go). The key is capped at 80 octets, which is the floor Section 4.5 recommends and Linux's own TCP_MD5SIG_MAXKEYLEN. rfc/short/rfc2385.md |
✕ |
| RFC 5082 | GTSM and TTL security | Supported on Linux | 4 gated: 3 proven, 0 annotated, 1 untested | Per-peer BGP GTSM through connection { ttl { max; set; min } }: parseTTLSettings derives an outgoing TTL of 255 and an inbound floor of 255-N+1 from ttl max N (internal/component/bgp/reactor/config.go), tuneTCPConnectionForSettings installs IP_TTL / IPV6_UNICAST_HOPS and IP_MINTTL / IPV6_MINHOPCOUNT on the connected socket (internal/component/bgp/reactor/session_connection.go, internal/core/network/ttl_linux.go), and listenTTLForListener carries the same outgoing TTL onto the listen socket so a GTSM peer that dials in does not drop the SYN-ACK (internal/component/bgp/reactor/reactor.go). BFD sets IP_TTL=255 and IPV6_UNICAST_HOPS=255 on transmit and gates the received TTL (internal/component/bfd/transport/udp_linux.go, passesTTLGate in internal/component/bfd/engine/loop.go). VRRP sends and requires TTL 255 (internal/plugins/vrrp/packet/validate.go). Requirements bound per requirement in. rfc/requirements/rfc5082.md |
The socket options are Linux-only: the non-Linux build returns an unsupported error and leaves the OS default (internal/core/network/ttl_other.go). Dynamic GTSM capability negotiation is not offered. RFC 5082 Section 2.1 neither assumes nor defines one, ze configures GTSM statically per peer, and the obligation conditional on running such a negotiation is excluded as feature-out-of-scope in. That absent feature is an implementation gap a later scope decision can revisit, and not a conformance gap. rfc/extraction/rfc5082.json |
| RFC 5925 | TCP-AO | Unsupported | ✕ | None claimed. | Not implemented. |
BGP attributes, families, policy, and NLRI
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 1997 | Standard communities | Supported | 5 gated: 5 proven, 0 annotated, 0 untested | 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 ./le 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 | ∿ | 4 gated: 4 proven, 0 annotated, 0 untested | 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. send bgp <selector> blackhole, and send bgp <selector> 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/bgp-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 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/extraction/rfc7999.json |
✕ |
| RFC 4360 | Extended communities | Supported | 6 gated: 2 proven, 4 annotated, 0 untested | Extended community attribute parsing, encoding, JSON, and policy use. | ✕ |
| RFC 5701 | IPv6 extended communities | ∿ | 4 gated: 3 proven, 1 annotated, 0 untested | 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 | 7 gated: 4 proven, 3 annotated, 0 untested | LARGE_COMMUNITY parsing, validation, duplicate removal, JSON, and RFC 7606 length checks. | ✕ |
| RFC 4456 | Route Reflection | Supported | 6 gated: 5 proven, 1 annotated, 0 untested | ORIGINATOR_ID, CLUSTER_LIST, route-reflector plugin behavior, cluster checks. | ✕ |
| RFC 7311 | AIGP attribute | ∿ | 5 gated: 4 proven, 1 annotated, 0 untested | 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. Ze sends the attribute optional and non-transitive (flags 0x80), and discards a received AIGP whose transitive bit is set (§3.2) with the rest of the UPDATE processed. 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, removable only by explicit operator policy. Gated as a gap in. rfc/short/rfc7311.md |
| RFC 4364 | BGP/MPLS IP VPNs | ∿ | 8 gated: 0 proven, 8 annotated, 0 untested | VPNv4 NLRI, RD, labels, route config, encode, and decode. | ✕ |
| RFC 4659 | VPNv6 | ∿ | 16 gated: 2 proven, 14 annotated, 0 untested | 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 | ∿ | 34 gated: 4 proven, 30 annotated, 0 untested | 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 | ∿ | 17 gated: 0 proven, 17 annotated, 0 untested | 20-bit label stack encoding and validation used by labeled unicast and VPN NLRI. | ✕ |
| RFC 4761 | VPLS NLRI | ∿ | 18 gated: 0 proven, 18 annotated, 0 untested | L2VPN VPLS family registration, encode, decode, and route config. | ✕ |
| RFC 4762 | VPLS using LDP signaling | Unsupported | ✕ | None. This row claimed support until 2026-08-30 on evidence that belongs to RFC 4761, the BGP-signalled VPLS this daemon implements and which has its own row above. The two RFCs are the competing signalling planes for the same service, and ze speaks only the BGP one. | LDP-signalled VPLS is absent: no LDP PWid or Generalized PWid FEC, no VPLS pseudowire signalling over LDP, and no LDP session driven by a VPLS instance. |
| RFC 7432 | EVPN | ∿ | 82 gated: 3 proven, 79 annotated, 0 untested | 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 (neither the Ethernet A-D per ES route nor the Ethernet Segment route can be originated from a session: NewEVPNType1 and NewEVPNType4 (internal/component/bgp/plugins/nlri/evpn/encode.go) are reached only from the offline ze bgp encode hex tool, while the session origination path buildEVPNFromParams admits route types 2, 3 and 5 only. No ESI Label extended community is attached on any path, 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) | ∿ | 14 gated: 2 proven, 12 annotated, 0 untested | 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 | ∿ | 22 gated: 9 proven, 13 annotated, 0 untested | 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, traffic-marking reserved bits ignored on decode, and lowering to the firewall; tests bound per requirement in. rfc/requirements/rfc8955.md |
Five 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; and RFC8955-6-3 -- no revalidation when unicast routes change. rfc/short/rfc8955.md |
| RFC 8956 | IPv6 FlowSpec | ∿ | 9 gated: 0 proven, 9 annotated, 0 untested | 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) | ∿ | 12 gated: 2 proven, 10 annotated, 0 untested | 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 | ∿ | 26 gated: 8 proven, 18 annotated, 0 untested | 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 |
Four MUST gaps annotated in: 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) | ∿ | 48 gated: 22 proven, 26 annotated, 0 untested | 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, RFC 9552 §8.2.2 syntactic validation of the BGP-LS Attribute on the receive path (validateBGPLSAttr, internal/component/bgp/message/rfc7606_bgpls.go) with 'Attribute Discard' handling for a malformed one, RFC 9552 §8.2.2 syntactic validation of the Link-State NLRI on the receive path (validateBGPLSNLRISyntax and RetainWellFormedNLRI, internal/component/bgp/message/rfc7606_bgpls_nlri.go) with 'NLRI discard' for a skipable error and session reset for a length error that leaves the UPDATE unprocessable, every descriptor's sub-TLVs emitted in the canonical order Section 5.1 defines -- ascending by TLV type across node, link and prefix descriptors, and, among repeated sub-TLVs of one type, ascending by Length then by Value (addressTLVs and srv6SIDsOrdered, internal/component/bgp/plugins/nlri/ls/types_descriptor.go) -- so one node never encodes to two keys, 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 |
One MUST gap annotated in: RFC9552-5.3-2 -- an oversized forwarded UPDATE that cannot be split is dropped whole instead of having the BGP-LS Attribute discarded first. rfc/short/rfc9552.md |
| RFC 9085 | BGP-LS Segment Routing extensions | ∿ | 12 gated: 0 proven, 12 annotated, 0 untested | 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 | ∿ | 13 gated: 0 proven, 13 annotated, 0 untested | 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 | ∿ | 12 gated: 0 proven, 12 annotated, 0 untested | 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 | ∿ | 4 gated: 0 proven, 4 annotated, 0 untested | 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 | Future | 133 gated: 0 proven, 0 annotated, 133 untested | MCAST-VPN NLRI decode and encode primitives, the Section 4 type-and-length NLRI split (splitMVPN, internal/core/bgp/nlri/nlrisplit/mvpn.go), a config route parser for source-active, shared-tree join and source-tree join routes, the 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. This is NLRI carriage, not MVPN: no PMSI tunnel is built or parsed, and no multicast state is created. |
Out of scope by owner decision, 2026-09-01, and tracked for future development. Requirements bound per line in, which declares all 133 MUST-level obligations: 132 have no producer in Ze. MVPN is not offered, so the absence is an implementation gap a later scope decision can revisit, and no conformance gap is claimed. rfc/short/rfc6514.md |
| RFC 9012 | Tunnel Encapsulation attribute | ∿ | 75 gated: 15 proven, 60 annotated, 0 untested | 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 | ∿ | 25 gated: 7 proven, 18 annotated, 0 untested | 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), the Section 8 boundary removes it on egress toward an EBGP peer the operator has not configured for propagation, on every rail that writes an UPDATE: the two forward rails, the two origination rails and the API/readvertise announce rail all ask prefixSIDAllowedTo (internal/component/bgp/reactor/forward_prefix_sid.go, internal/component/bgp/reactor/reactor_api_batch.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. | Ten 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 nine 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. |
| RFC 9252 | BGP SRv6 Service TLV (Prefix-SID) | ∿ | 19 gated: 6 proven, 13 annotated, 0 untested | Prefix-SID attribute (code 40) SRv6 L3/L2 Service TLV parse, SID Information Sub-TLV and SID Structure Sub-Sub-TLV extraction with the errata-7817 sum bound (internal/component/bgp/plugins/rib/pool/srv6sid.go); Section 3.2.1 transposition undone for the IPv4 and IPv6 VPN families, whose Section 5.1 label field is read out of the NLRI the route is keyed by (internal/core/bgp/nlri/nlrisplit/transposition.go) and merged back into the partial SID (internal/component/bgp/plugins/rib/rib_bestchange.go, srv6SIDFromResult); 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. | EVPN transposition is not implemented: Section 6 puts the label field at a different NLRI offset per route type, in the PMSI Tunnel Attribute for Route Type 3 and in the ESI Label extended community for Route Type 1 per-ES, and Route Type 2 has two label fields bound to different Service TLVs. An EVPN route whose Prefix-SID declares a transposition therefore yields no SID rather than the partial one. Eight MUST gaps annotated in rfc/short/rfc9252.md: RFC9252-4.1-1/6.1-1/6.2-1 -- Transposition Length is now bounded against the 20-bit VPN and 24-bit EVPN label field widths but still not against FL or 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 | ∿ | 96 gated: 60 proven, 36 annotated, 0 untested | 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 | ∿ | 22 gated: 1 proven, 21 annotated, 0 untested | 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 | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 7854 | BMP v3 | ∿ | 13 gated: 6 proven, 7 annotated, 0 untested | 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 | 10 gated: 7 proven, 3 annotated, 0 untested | Adj-RIB-Out direction flag handling and sent-route monitoring. | ✕ |
| RFC 9069 | BMP Loc-RIB monitoring | Supported | 15 gated: 7 proven, 8 annotated, 0 untested | Loc-RIB Instance Peer (Peer Type 3): per-peer flags cleared, zero Peer Address, Peer AS set to the router's own 4-octet ASN, Peer BGP ID set to the local router-id, a fabricated OPEN carried in both the sent and the received field of the Peer Up (Section 5.2) advertising the 4-octet ASN capability and one address-family capability per family the dump delivers, a Peer Down carrying reason code 6, the VRF/Table Name Information TLV (type 3) naming that Loc-RIB global on the Peer Up and repeated on the Peer Down (Section 5.2.1), a per-peer Timestamp that is the install time of an incremental change and ZERO where that time is unavailable (Section 5.1), the receiver reading the address-family capabilities off a Peer Up's OPEN and reporting them per peer (Section 6.1.1), 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). Closed 2026-08-31, found by an extraction walk of the RFC's own text: (3) the Loc-RIB per-peer header sent Peer AS 0 against Section 5.1's "Set to the primary router BGP autonomous system number (ASN)", and the summary declared the defect as the requirement; (4) the Loc-RIB Peer Up carried two zero-length OPENs against Section 5.2's "This is a fabricated BGP OPEN message. Capabilities MUST include the 4-octet ASN", and its summary row did the same; (5) the RECEIVER shared the belief -- decodePeerUp skipped OPEN extraction for Peer Type 3, so a conformant Loc-RIB Peer Up from any implementation was misparsed from the first Information TLV onward; (6) the Loc-RIB Peer Down carried reason 2 against Section 5.3's "The Peer Down notification MUST use reason code 6". Closed 2026-09-01 by the extraction sign-off of the same text: (7) the per-peer Timestamp read a wall clock, which dated every route of a full-table replay to the moment the collector connected, where Section 5.1's own answer for an install time the sender does not know is zero; (8) no VRF/Table Name TLV was sent, against Section 5.2.1's "The default value of "global" MUST be used for the default Loc-RIB instance with a zero-filled distinguisher"; (9) the receiver read the capabilities of no Peer Up OPEN, against Section 6.1.1's "A BMP receiver MUST process these capabilities to know which peer belongs to which address family", and keyed every Peer Type 3 peer of one router by its zero-filled address, so two Loc-RIB instances shared one RIB-In pool. TWO FEATURES ARE OUT OF SCOPE and are implementation gaps rather than conformance gaps. VRF: ze runs one Loc-RIB, the default global instance, and offers no VRF and no second named table (owner decision, 2026-09-01), so the Section 5.2.1 obligations conditional on a configured name or on multiple names for one Loc-RIB do not bind it. Loc-RIB FILTERING: Section 5 makes it optional -- "a subset of Loc-RIB routes MAY be sent to a BMP collector by setting the F flag" -- and ze sends the whole Loc-RIB, so the F flag stays 0 and the Section 6.1.2 table-name obligation for multiple filters against one Loc-RIB does not bind it either. A later scope decision can revisit both. |
| RFC 6811 | RPKI origin validation | Supported | 5 gated: 5 proven, 0 annotated, 0 untested | 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 | ∿ | 56 gated: 8 proven, 48 annotated, 0 untested | 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) | ∿ | 39 gated: 5 proven, 34 annotated, 0 untested | 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 | A Profile for Route Origin Authorizations (ROAs) | Unsupported | 10 gated: 6 proven, 4 annotated, 0 untested | Nothing. RFC 9582 profiles the ROA signed object itself: a CMS envelope carrying a RouteOriginAttestation, an EE certificate bearing an RFC 3779 IP address delegation extension, and a certification path to a trust anchor. No ROA object exists anywhere in the ze process. parsePrefixPDU reads an already-validated payload off an RTR PDU at fixed byte offsets, so the decode, the extension reading and the path validation this RFC specifies all happen in the RPKI cache ze dials. |
Every obligation, and implementing them means a relying-party validator: a CMS/DER decoder (RFC 5652 and RFC 6488), an RFC 3779 extension reader, X.509 path validation to a trust anchor, and an RRDP or rsync fetcher. That is what Routinator and rpki-client are, and ze delegates it deliberately. This row read Supported until 2026-09-01, and the claim was never about this RFC. still declares thirteen ids describing ASPA PDUs and RTR version negotiation, which are draft-ietf-sidrops-8210bis: RFC 9582 has no Section 5.12 and its Section 7 is IANA Considerations, so each id cites a section its own document does not have. The obligations are real and ze meets them; only the attribution is wrong. Correcting it is, which the ledger's own ratchets refuse until they learn to let proof follow an obligation to another document. rfc/short/rfc9582.mdplan/pre-release/spec-rfc-requirement-reattribution.md |
| RFC 5880 | BFD base | ∿ | 96 gated: 73 proven, 23 annotated, 0 untested | 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, Simple Password and Keyed / 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). Final-packet rate limiting is absent by design and annotated not-applicable. IPv6 transport coverage is tracked with BFD. rfc/short/rfc5880.md |
| RFC 5881 | BFD single-hop | ∿ | 23 gated: 12 proven, 11 annotated, 0 untested | 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 | ∿ | 3 gated: 1 proven, 2 annotated, 0 untested | BFD integration model used by BGP and static next-hop tracking. | Same BFD partial status. |
| RFC 5883 | BFD multi-hop | ∿ | 8 gated: 6 proven, 2 annotated, 0 untested | 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 | ✕ | The document allocates one registry value and states three SHOULD statements. It states no MUST-level obligation anywhere. is the walk that bounds that claim, and is the checklist. Section 3. Peer.runBFDSubscriber (internal/component/bgp/reactor/peer_bfd.go) turns a BFD Down or AdminDown transition into teardownAutomatic(message.NotifyCeaseBFDDown. ). The session then ends with a Cease NOTIFICATION carrying subcode 10 rather than waiting for the hold timer. The subscriber runs only where the operator opted in with bgp peer connection bfd. message.CeaseSubcodeString (internal/component/bgp/message/notification.go) renders the value as "BFD Down" in the log and in the CLI. Section 4, first statement. Peer.recordNotification (internal/component/bgp/reactor/peer_stats.go) stores the code and subcode of a NOTIFICATION that reached the wire. It records one ze sent and one ze received alike. lastErrorString (internal/component/bgp/plugins/cmd/peer/summary.go) renders them as the last-error field of show bgp peer. rfc/extraction/rfc9384.jsonrfc/short/rfc9384.md |
RFC9384-4-1 is met only where the NOTIFICATION reached the peer. sendNotificationWithin (internal/component/bgp/reactor/session_write.go) calls onNotifSent after a successful write alone. A teardown whose NOTIFICATION never reached the wire therefore records the reason in the log and leaves last-error empty. Section 4 is written for exactly that case. RFC9384-4-2 is conditional on RFC 8538 Hard Reset procedures, and this page records RFC 8538 as Unsupported. So the condition that statement opens with does not arise. Both are SHOULD statements, so neither is a gap at MUST level. |
IS-IS, OSPF, MPLS, and traffic engineering
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 1195 | Integrated IS-IS for IPv4 | Experimental | 10 gated: 1 proven, 9 annotated, 0 untested | 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 | 7 gated: 7 proven, 0 annotated, 0 untested | 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 | 18 gated: 9 proven, 9 annotated, 0 untested | 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 | 9 gated: 7 proven, 2 annotated, 0 untested | Interface and level key-chain authentication. | Same IS-IS experimental status. |
| RFC 5310 | IS-IS generic crypto authentication | Experimental | 9 gated: 4 proven, 5 annotated, 0 untested | HMAC-SHA authentication path. | Same IS-IS experimental status. |
| RFC 5305 | IS-IS wide metrics and TE TLVs | Experimental | 8 gated: 5 proven, 3 annotated, 0 untested | 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 |
TE sub-TLVs (6/8) and TLV 134 (TE Router ID) are not implemented (no IS-IS TE). |
| RFC 3787 | IS-IS interoperability guidelines | ∿ | 3 gated: 2 proven, 1 annotated, 0 untested | 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 | 4 gated: 3 proven, 1 annotated, 0 untested | Up/down bit retention and redistribution behavior. | Same IS-IS experimental status. |
| RFC 5308 | IS-IS IPv6 | Experimental | 7 gated: 7 proven, 0 annotated, 0 untested | 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 | ∿ | 25 gated: 24 proven, 1 annotated, 0 untested | 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 ./le 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 | 17 gated: 17 proven, 0 annotated, 0 untested | 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 both address families: mandatory border-router defaults with no operator gate, no-summary defaults through the summary path (Type-3 for OSPFv2, Inter-Area-Prefix for OSPFv3), no summary-LSA default where summary routes ARE imported (Section 2.7 MUST NOT), an internal router's default-originate held inert in a no-summary NSSA (Section 1.3 mutual exclusivity), and the P-bit and suppressed-summary-import gates on installing a received Type-7 default, each gate proven permissive on a router that is not an NSSA border router. |
The Section 2.4 default-route origination dispatches on address family in applyNSSADefaults (internal/plugins/ospf/nssa.go), so an OSPFv3 NSSA border router originates the 0x2007 NSSA-LSA that RFC 5340 Section 4.4.3.7 defines rather than the OSPFv2 0x0007. Both halves are unit-proven in each family, both families reach the operator's show ospf database nssa-external subview under, and OSPFv2 origination is additionally proven against FRR by test/interop/scenarios/ospf-stub-nssa-frr. Two things are still owed, tracked by: OSPFv3 has no interop scenario, so no peer daemon has read a Ze OSPFv3 NSSA default, and neither family has an interop scenario for the two install-side gates; and three sites compute NSSA border-router status independently, so the advertised Router-LSA B-bit and the originated default can disagree across a backbone transition. Section 2.2 Type-7 address-range aggregation into one Type-5 stays unimplemented; it is a MAY. Note that the checklist cannot record an address-family difference: its requirement ids carry no address-family dimension, so a tagged test on either path satisfies it for both. Same OSPF experimental status. test/ospf/ospf-nssa-abr-default.citest/ospfv3/ospfv3-nssa-abr-default.ciplan/immediate/spec-ospf-rfc3101-nssa-defaults.mdrfc/short/rfc3101.md |
| RFC 5709 | OSPFv2 HMAC-SHA authentication | Experimental | 15 gated: 13 proven, 2 annotated, 0 untested | OSPFv2 cryptographic authentication path. | Same OSPF experimental status. |
| RFC 7474 | OSPFv2 manual-key security extension | Experimental | 10 gated: 7 proven, 3 annotated, 0 untested | 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) | ∿ | 23 gated: 16 proven, 7 annotated, 0 untested | 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 ./le 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 | 16 gated: 4 proven, 12 annotated, 0 untested | 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 ./le 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 | 17 gated: 0 proven, 17 annotated, 0 untested | 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 | ∿ | 27 gated: 4 proven, 23 annotated, 0 untested | Manual AH and ESP IPsec on configured OSPFv3 interfaces: config validation, SA and policy lifecycle over kernel XFRM, the XFRM readiness check, and the kernel drop counters. Proven against FRR ospf6d by the interop scenarios ospf-ipsec-frr and ospf-ipsec-ah-frr, which take the adjacency to Full and then read the installed transport-mode SA and policy on both peers. | Virtual-link IPsec is unimplemented, which is five MUSTs across §9 and §11. Manual keying only: no rekey procedure, so a key change drops the adjacency. |
| RFC 4577 | OSPF as the PE/CE protocol for BGP/MPLS IP VPNs | Not supported | 36 gated: 2 proven, 34 annotated, 0 untested | 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 ./le 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 | 9 gated: 7 proven, 2 annotated, 0 untested | Opaque LSA framework and retention. | Same OSPF experimental status. |
| RFC 3630 | OSPFv2 Traffic Engineering LSA | Experimental | 5 gated: 1 proven, 4 annotated, 0 untested | TE LSA body and sub-TLV support. | Same OSPF experimental status. |
| RFC 5392 | OSPF inter-AS TE (OSPFv2) | Experimental | 12 gated: 3 proven, 9 annotated, 0 untested | 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 | 11 gated: 1 proven, 10 annotated, 0 untested | Router Information LSA body and multi-instance ordering. | Same OSPF experimental status. |
| RFC 7684 | OSPF Extended Prefix and Link LSAs | Experimental | 8 gated: 5 proven, 3 annotated, 0 untested | Extended Prefix and Extended Link LSA bodies and malformed TLV handling. | Same OSPF experimental status. |
| RFC 3623 | OSPFv2 Graceful Restart | Experimental | 13 gated: 4 proven, 9 annotated, 0 untested | 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 | 4 gated: 4 proven, 0 annotated, 0 untested | Restarter and helper behavior for OSPFv3. | Same OSPF experimental status. |
| RFC 8665 | OSPF Segment Routing extensions | ∿ | 47 gated: 28 proven, 19 annotated, 0 untested | 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 ./le 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 | ∿ | 31 gated: 21 proven, 10 annotated, 0 untested | 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 ./le 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 | Future | 38 gated: 0 proven, 0 annotated, 38 untested | Three of the seven Extended LSA types are framed and originated, and only under segment-routing: E-Router (0x2021), E-Intra-Area-Prefix (0x2029) and E-Inter-Area-Prefix (0x2023), through EncodeExtendedLSABody (internal/plugins/ospf/v3/packet/lsa_extended.go). Receipt decoding reads Prefix-SIDs and nothing else. No SPF calculation reads an Extended LSA. |
Out of scope as a document by owner decision, 2026-09-01, and tracked for future development. Requirements bound per line in. E-Network, E-Inter-Area-Router, E-AS-External, E-Type-7 and E-Link have no producer, nothing validates an Extended LSA body before it is installed, and there is no migration or compatibility mode. The LS types Ze emits carry the U-bit clear where Section 2 requires it set, so they do not flood past an OSPFv3 router that does not support them; that one is a defect rather than a scope decision. rfc/short/rfc8362.md |
| RFC 5286 | Loop-Free Alternate FRR | Experimental | 6 gated: 3 proven, 3 annotated, 0 untested | 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 | 8 gated: 5 proven, 3 annotated, 0 untested | 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 | 14 gated: 7 proven, 7 annotated, 0 untested | 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 | 13 gated: 5 proven, 8 annotated, 0 untested | 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 | 6 gated: 2 proven, 4 annotated, 0 untested | 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 |
Two MUST gaps gated in: the receive path does not verify the RSVP checksum or drop bad-checksum messages (3.1); and PATH is sent without the IP Router Alert option. rfc/short/rfc2205.md |
| RFC 4090 | RSVP-TE Fast Reroute | Experimental | 12 gated: 12 proven, 0 annotated, 0 untested | Facility backup behavior. A PATH carrying a DETOUR object is rejected with a PathErr, as Section 4.2 requires of an LSR without one-to-one backup. | One-to-one detour backup was explicitly split to later work. |
First-hop redundancy
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 9568 | VRRPv3 (IPv4 and IPv6) | ∿ | 59 gated: 38 proven, 21 annotated, 0 untested | 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, Accept_Mode enforced on the dataplane (an Active router that is neither the address owner nor configured Accept_Mode True does not accept packets addressed to the virtual addresses, while still answering ARP and Neighbor Discovery for them and forwarding for the virtual MAC), including the Section 6.1 carve-out that never drops IPv6 Neighbor Solicitations or Advertisements. | Two gaps gated in. 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 | 39 gated: 31 proven, 8 annotated, 0 untested | 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). |
✕ |
| RFC 5798 | VRRPv3 (obsoleted by RFC 9568) | ∿ | 55 gated: 33 proven, 0 annotated, 22 untested | 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. Enrolled 2026-09-01: declares 80 requirements, 55 of them MUST-level, and every one carries a {superseded} marker naming where RFC 9568 states it. 33 of those 55 are proven under RFC5798 ids in both polarities, each polarity carrying a discrimination record. 7 more carry a recorded positive proof and no negative case, because the producer writes a constant no input changes: RFC5798-5.1.1.3-1, RFC5798-6.4.3-2, RFC5798-7.2-1 through RFC5798-7.2-4, and RFC5798-8.2.2-1. The 15 that carry no tag are 9 whose RFC 9568 twin is recorded {not-applicable}, 2 whose twin is recorded {gap} (RFC5798-6.4.3-3 and RFC5798-6.4.3-4, the IPv6 Neighbor Advertisement Router flag and the Router Advertisement a Master owes), 2 that RFC 9568 dropped rather than restated (RFC5798-7.4-1 and the Token Ring appendix RFC5798-A.2-1), RFC5798-5.1.2.3-1, whose only evidence is an integration-gated test the privileged QEMU suite runs and ./le rfc discriminate-record cannot reach, and RFC5798-7.1-3, which ze does not fully implement: Section 7.1 makes the receiver discard an advertisement when the local router is the IPvX address owner, and Decode (internal/plugins/vrrp/packet/validate.go) verifies only the VRID half, because RFC 9568 erratum 8298 lowers the address-owner half to a log. One RFC 5798 obligation is NOT a restatement: RFC5798-5.2.8-1 puts a pseudo-header under the checksum for both address families, which is the divergence this row describes. RFC 5798 Section 5.2.8 cites the pseudo-header "as defined in Section 8.1 of [RFC2460]", an IPv6-only shape, so what ze and the deployed base compute for IPv4 is the classic IPv4 pseudo-header (pseudoSumV4Legacy) rather than the shape that sentence names. rfc/short/rfc5798.mdinternal/plugins/vrrp/packet/checksum.go |
Access, AAA, PPP, and subscriber services
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 8414 | OAuth 2.0 Authorization Server Metadata (MCP resource server) | ∿ | 7 gated: 2 proven, 5 annotated, 0 untested | 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 | ∿ | 20 gated: 16 proven, 4 annotated, 0 untested | 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/interop-l2tp/scenarios/03). LAC data-plane bridge (A-4) is QEMU/CAP_NET_ADMIN-gated. |
| RFC 1661 | PPP LCP | ∿ | 66 gated: 26 proven, 40 annotated, 0 untested | 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-four 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); (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 | ∿ | 7 gated: 5 proven, 2 annotated, 0 untested | PAP authentication option through PPP auth handling. | Carries the L2TP and PPPoE Partial status. |
| RFC 1994 | PPP CHAP (MD5) | ∿ | 17 gated: 5 proven, 12 annotated, 0 untested | CHAP-MD5 authenticator (LNS) and peer (PPPoE client): Challenge/Response/Success/Failure codec, per-call 16-octet random Challenge, changing Identifier, MD5(id||secret||challenge) validation via local user table and RADIUS CHAP-Password, LCP Auth-Protocol negotiation. | Three MUST gaps in rfc/short/rfc1994.md: RFC1994-4.1-2 -- one Challenge is sent then the session fails closed on timeout (no retransmission); RFC1994-4.1-9 -- a repeated Response with the current Challenge Identifier is silently dropped rather than re-answered with the prior reply Code (session_run.go); RFC1994-2.3-1 -- the CHAP secret has no 1-octet minimum, so an empty password is accepted. |
| RFC 1332 | IPCP | ∿ | 6 gated: 3 proven, 3 annotated, 0 untested | 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 | ∿ | 4 gated: 3 proven, 1 annotated, 0 untested | 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 | ∿ | 16 gated: 6 proven, 10 annotated, 0 untested | Interface-Identifier NCP: independent FSM, generation, Configure-Req/Ack/Nak/Reject, RA/DHCPv6-PD after Opened. | Gaps in rfc/short/rfc5072.md: ze's own TENTATIVE interface identifier (requestIPv6CPInterfaceID/generateIPv6CPInterfaceID) does not zero the u/l bit (4.1-11; the Nak-suggested identifier's u/l bit is fixed, 4.1-9); no 1280 MTU floor for IPv6 sessions (2-2, minIPMTU=68); no last-Nak-suggestion oscillation break (4.1-8). IPv6 address/prefix assignment is outside IPv6CP (DHCPv6-PD/SLAAC). |
| RFC 2516 | PPPoE | ∿ | 22 gated: 15 proven, 7 annotated, 0 untested | 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 |
✕ |
| RFC 2865 | RADIUS authentication | Supported for subscriber access | 30 gated: 25 proven, 5 annotated, 0 untested | Access-Accept profile extraction, Filter-Id, Session-Timeout, Idle-Timeout, VSAs, pool selection. | Operator/admin login RADIUS is a separate backend under system/authentication/radius, and its auth-method leaf selects which credential the Access-Request carries, one and never two per RFC 2865 Section 4.1 ((*radiusAuthenticator).credential); the profile attributes above are subscriber-access only. internal/component/radius/authenticator.go |
| RFC 2866 | RADIUS accounting | Supported for subscriber access | 16 gated: 16 proven, 0 annotated, 0 untested | Start, Stop, and Interim-Update accounting records, each carrying Event-Timestamp, Calling-Station-Id and Acct-Delay-Time, and the Stop record carrying the §5.10 Acct-Terminate-Cause (authradius/acct.go buildAcctPacket, radius/client.go setAcctDelayTime). An operator holds any of those four back per record type with l2tp auth radius attributes exclude, which never reaches Acct-Status-Type, Acct-Session-Id or the NAS identity (authradius/exclude.go excludableAttributes, attributeExclusions.filter). |
Admin/operator RADIUS accounting is not wired; the admin backend is authentication-only. |
| RFC 2869 | RADIUS extensions | Supported for subscriber access | 11 gated: 8 proven, 3 annotated, 0 untested | Gigaword counters and selected accounting extensions. | Scoped to subscriber access. |
| RFC 3579 | RADIUS/EAP | ∿ | 31 gated: 17 proven, 14 annotated, 0 untested | Ze is the NAS and runs the RADIUS/EAP conversation for operator login, with auth-method eap-md5 or eap-mschapv2. It encapsulates one EAP packet per RADIUS packet in consecutive EAP-Message attributes split at 253 octets (appendEAPMessage, eapPacketFrom), signs every EAP-bearing Access-Request (SignMessageAuthenticator), silently discards a reply whose Message-Authenticator does not verify ((*Client).dispatchResponse), and answers each Access-Challenge with the peer's EAP-Response and the challenge's State unmodified (authenticateEAP). It copies the peer's own EAP-Response/Identity Type-Data into User-Name on every request of the conversation, validates each server EAP header before the peer sees it, processes the EAP-Message attributes before it reads the reply code, and never offers a password credential in place of the configured EAP method. Ze answers EAP itself, as the peer, rather than passing frames through. internal/component/radius/eap.gointernal/component/radius/packet.gointernal/component/radius/client.gointernal/component/radius/authenticator_eap.go |
Fourteen MUST-level requirements are unproven or unreached, and none of them changes what an operator can configure. The pass-through architecture accounts for most: ze answers EAP for the operator and forwards no EAP frame, so Section 2.1's EAP-Start and the Nak relay have no path, and the Section 2.2 Error-Cause 202 branch cannot fire because ze holds no queue of EAP-Responses for its antecedent to match. Four prohibitions hold by construction and are tagged by nothing, because a test would have to observe a packet no code path can build: Sections 2.1, 2.6.3 and 2.6.5. EAP-TLS is implemented in the peer (internal/core/eap) and not offered by auth-method, which needs an operator certificate and key. Ze's PPP NAS never negotiates EAP, so the Section 4.3.6 obligations that bind a NAS whose peers require EAP are unreached. The IPsec-for-RADIUS profile of Section 4.2 is absent: ze offers no way to run RADIUS over an IPsec SA and no zero-length shared secret. Section 4.3.4's ban on sharing a secret between an EAP NAS and a User-Password NAS is not checked at config load. |
| RFC 5176 | RADIUS CoA and Disconnect Message | Supported for subscriber access | 22 gated: 21 proven, 1 annotated, 0 untested | CoA/DM listener for RADIUS-initiated changes and disconnects: Request Authenticator and optional Message-Authenticator verification, source-address allow list, duplicate detection and cached replay, Event-Timestamp window, mandatory-attribute handling with Error-Cause 401, Service-Type refusal with 405, multiple-match refusal with 508, and Proxy-State and State echoed unread. Tests bound per requirement in, and the checklist is bounded by. . rfc/requirements/rfc5176.mdrfc/extraction/rfc5176.json |
Scoped to subscriber access. Two OPTIONAL features of the RFC are out of scope, so the obligations conditional on them are excluded rather than gated: the Section 3.2 "Authorize Only" Service-Type exchange, which ze answers with a CoA-NAK and Error-Cause 405, and the RFC 2865 Section 5.29 Termination-Action re-authorization, for which ze sends no Access-Request. |
| RFC 8907 | TACACS+ | ∿ | 12 gated: 3 proven, 9 annotated, 0 untested | 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 | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 7296 | IKEv2 | ∿ | 222 gated: 222 proven, 0 annotated, 0 untested | 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 OBSERVED 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 2.23.1 address substitution: with a NAT on the path, both roles replace the transport-mode selector addresses before those selectors are used for anything except storing the originals. The responder substitutes the TSi address with the observed remote address when the peer is behind a NAT, and the TSr address with its own local address when it is, before the policy lookup that is Ze's SPD lookup. The initiator substitutes the mirror pair before the answer is checked against what it proposed and before the SAD entry is created. The substituted address is always one this node observed, never one the peer asserted, so the narrowing guard still refuses an answer outside the proposal. The SA records which SIDE each translation is on, separately from whether a NAT is present, and stores the pre-substitution addresses the section requires be kept. Tunnel mode and a NAT-free path are unchanged. Proven against strongSwan across a netfilter box that really rewrites both addresses, in test/interop-ipsec/scenarios/real-nat-transport-ze-initiator, real-nat-transport-ze-responder and real-nat-tunnel-control (engine/ts_nat_substitute.go, engine/ts_narrow.go, engine/sa.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. The two holders that live on the peer SESSION rather than on the SA are released on the same exit: the Diffie-Hellman private value of a rekey that never got its answer, and a whole IKE SA built while answering the peer's rekey and never confirmed by the peer's Delete. The session outlives the SA, so either one left behind would carry key material into the next reconnect cycle (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.7 Extended Sequence Numbers: Ze keys a 32-bit ESP sequence space, so it offers and answers Transform Type 5 value 0 and selects that value from a proposal that offers both. A proposal offering value 1 alone is refused with NO_PROPOSAL_CHOSEN rather than answered with a transform the peer never proposed, and an answer selecting value 1 ends the exchange on the initiator side (engine/responder.go, engine/initiator.go). Proven against strongSwan in test/interop-ipsec/scenarios/esn-extended-only-refused and esn-both-offered. 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). Section 2.2 two counters: each end keeps its own next Message ID. A responder-role SA raises its first request at the id a conforming peer expects. It took the peer's id before this fix, so it raised no DPD, no Delete and no rekey (engine/responder.go, engine/msgid.go). Section 2.9 narrowing orientation: the responder narrows against the orientation of the exchange in hand. The IKE SA role is a different question. A Child SA rekey the peer starts on a tunnel Ze initiated is answered rather than refused with TS_UNACCEPTABLE (engine/ts_narrow.go). Section 2.8 rekey on the XFRM dataplane: the replacement Child SA inherits the retired pair's selectors and their orientation. The make-before-break window holds one policy, and retiring the superseded pair removes no selector the live pair still needs. measures it on the real backend with the two roles crossed (engine/rekey.go, engine/child.go). test/ipsec/ipsec-child-rekey-xfrm.ci |
✕ |
| RFC 4301 | IPsec architecture | ∿ | 20 gated: 6 proven, 14 annotated, 0 untested | Native control-plane SPD/SAD model projected to kernel XFRM. The SPD carries all three dispositions of Section 4.4.1: a negotiated Child SA produces the PROTECT entries, and the operator vpn ipsec policy list produces the BYPASS and DISCARD entries, each with a selector, a direction and a total order. A selector holds the local and remote prefixes, the next-layer protocol, and a local and a remote port as an any-or-one-exact value. IKEv2 Child SAs install tunnel mode, or transport mode when USE_TRANSPORT_MODE is negotiated and echoed, and the RFC 4552 OSPFv3 path installs manual transport-mode ESP or AH. SAD entries carry the SPI, the address pair, the mode, the protocol, the algorithms, the replay window and a time-based lifetime. Per-packet SPD and SAD processing is kernel-delegated to XFRM. |
Lowered from Supported on 2026-08-30, after an extraction walk of found architecture obligations Ze does not meet. The implementation work is, one phase per block. Section 6, ICMP processing: no control lets an administrator accept or reject unauthenticated ICMP error messages per ICMP type, and no check compares a protected transit ICMP error message payload header against the traffic selectors of the SA that carried it. Section 8, DF bit and PMTU: the DF treatment of a tunnel-mode SA is not configurable, and no per-SA PMTU value is held or aged. Section 4.4.2.1, SAD lifetimes: newLifetimeState (internal/component/ike/engine/rekey.go) assigns a time lifetime only, softBytes is never assigned, and the byte-count arm of softExpired is unreachable in a running daemon; designs the byte-count lifetime. Section 5.1.2.1, outer header: the DSCP value of the outer tunnel header is not mapped for the domain the packet enters. Section 4.4.1.1, selectors: a port selector holds any port or one exact port rather than the range the section defines, and SPParams carries no ICMP type or code field. Only that last item is gated in, as RFC4301-4.4.1.1-1, and its reason text is stale about ports. rfc/full/rfc4301.txtplan/immediate/spec-rfc4301-architecture-gaps.mdplan/spec-ipsec-lifetime-volume.mdrfc/short/rfc4301.md |
| RFC 4302 | Authentication Header | ∿ | 34 gated: 8 proven, 23 annotated, 3 untested | AH algorithm planning and RFC 4552 OSPFv3 use. | Scoped to configured manual IPsec support. No counter reset before sequence-number rollover, and no AH auditing surface. |
| RFC 4303 | ESP | Supported | 22 gated: 6 proven, 16 annotated, 0 untested | ESP SA parameter model, protocol 50, tunnel and transport modes, XFRM installation, OSPFv3 manual ESP. | ✕ |
| RFC 3948 | UDP encapsulation of ESP | ∿ | 14 gated: 8 proven, 6 annotated, 0 untested | NAT-T non-ESP marker, UDP 4500 encapsulation, NAT keepalive, XFRM UDP encap attributes. | Section 5.1 tunnel mode conflict (RFC3948-5.1-1) is a gap. Ze assigns no inner address to a remote peer, so it devises no way of preventing two peers behind one NAT from reaching it with the same self-chosen inner address. The section's RECOMMENDED remedy is a locally unique address per peer, and the allocator for it is written and unreached: Pool.Allocate has no non-test caller, and registerIKE discards the pool it builds. No engine code constructs a Configuration payload, so ze sends no CFG_REPLY. Closing it is. internal/core/eap/pool.gointernal/component/ike/engine/register.goplan/immediate/spec-ike-virtual-ip-assignment.md |
| RFC 4555 | MOBIKE | Unsupported | 6 gated: 0 proven, 6 annotated, 0 untested | 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 | 61 gated: 33 proven, 13 annotated, 15 untested | EAP framework inside IKEv2 IKE_AUTH, Success and Failure handling, and the Section 4.2 discards that stop a rogue authenticator bypassing the method: the peer reads an EAP-Success only once the method conversation concluded, drops an EAP-Failure once both ends indicated success, and both roles drop a Code outside 1-4 (PeerSession.Process and Session.Process, internal/core/eap). The peer answers Type 1 (Identity), Type 2 (Notification) and Type 3 (Nak): a Notification Request draws a five-octet Notification Response and its message reaches the operator log, and a Request for an authentication Type ze does not run draws a six-octet legacy Nak naming the configured method, until the peer has answered a method Request and Section 2.1 closes the Nak (PeerSession.handleRequest). A Type-254 Request draws the same legacy Nak, which is what Section 5.7 prescribes for a peer not equipped to interpret an Expanded Type. Type 4 (MD5-Challenge) runs on both roles and is the authentication { mode eap-md5 } an operator selects. It is never a default, and adopting a configuration that names it writes one warning quoting the RFC 7296 Section 2.16 sentence that discourages a method establishing no shared key (warnKeylessEAPModes). internal/core/eap/peer.gointernal/component/ike/engine/eap_auth.go |
Ze reads no Expanded Type (254), which Section 5 states as a SHOULD, and answers a Type-254 Request with the legacy Nak Section 5.7 prescribes rather than composing an Expanded Nak. Ze's authenticator sends no Notification Request, which RFC 3748 Section 5.2 states as an option ("An authenticator MAY send a Notification Request to the peer at any time when there is no outstanding Request, prior to completion of an EAP authentication method") and which the owner declined on 2026-09-01; Ze's peer answers one, which is the mandatory half. Two further features are absent by decision rather than by omission, and neither is a conformance gap. Ze's authenticator terminates every EAP method locally and does not act as a pass-through agent for a backend authentication server; Section 2 says "Support for pass-through is optional". Ze offers neither the One Time Password method (Type 5) nor the Generic Token Card method (Type 6), which Section 5 leaves to the implementation ("Implementations MAY support other Types defined here or in future RFCs"). A later scope decision can revisit any of the three. The Type 4 (MD5-Challenge) deviation authorized on 2026-08-30 was WITHDRAWN by the owner on 2026-09-01, who ordered the method implemented; both roles now run it. |
| RFC 2759 | MS-CHAPv2 (PPP and EAP inside IKEv2) | Supported | 12 gated: 9 proven, 3 annotated, 0 untested | Mutual authentication on the PPP path and the IPsec EAP path, with MPPE/MSK key derivation on the IPsec EAP path only. 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=). The EAP authenticator and peer roles are both implemented (internal/core/eap). The peer recomputes the expected Authenticator Response and compares it in constant time, and refuses the session when it does not match, so a Success packet is a claim the peer checks rather than one it trusts (handleMSCHAPv2Success, eap/peer.go). A refused credential draws an MS-CHAPv2 Failure packet (OpCode 4) carrying E=691, R=0, a fresh 32-digit C= challenge, V= and M=, rather than a bare EAP-Failure (sendFailure, eap/eap_mschapv2.go). | ✕ |
| RFC 5216 | EAP-TLS (EAP inside IKEv2) | ∿ | 21 gated: 14 proven, 7 annotated, 0 untested | 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. One reachability limit is not a conformance gap and is stated here so no reader assumes otherwise: the Section 2.3 derivation is a crypto/tls ExportKeyingMaterial call, and Go refuses that export on a TLS 1.2 session that did not negotiate the RFC 7627 extended master secret, so a peer such as strongSwan 5.9.14 cannot be authenticated over TLS 1.2 at all. That peer lands on TLS 1.2 by default rather than by limitation, and charon.tls.version_max = 1.3 moves the same build onto the RFC 9190 path that scenario eap-tls13 proves. Ze implements the derivation correctly and reports the refusal with the peer, the negotiated version, RFC 7627 and the operator's answers (eapTLS12ExportRefused, internal/core/eap/eap_tls.go); Go 1.27 removed the GODEBUG setting that once lifted the refusal and offers no replacement, and RFC 9190 covers the TLS 1.3 path, which needs none of it. |
One MUST gap in rfc/short/rfc5216.md. 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). RFC5216-5.4-2 is no longer among them: the peer re-checks the revocation status of the authenticator certificate once the CHILD_SA gives it a network, over https, and closes the SA when the responder reports it revoked (startServerCertRecheck, internal/component/ike/engine/postauth_revocation.go, reached from runEstablished). It runs whichever TLS version the exchange negotiated, which is what this RFC asks for, and the chain it re-reads is the one the peer accepted (eap.PeerSession.ServerChains). RFC5216-5.4-1 is no longer among them either: checkChainRevocation (internal/core/eap/revocation.go) checks every certificate on each verified chain except the trust anchor against the crl leaf-list its CA publishes, on the authenticator through tls.Config.VerifyConnection and on the peer through serverChainCheck.verifyConnection, and it is proven in both polarities on TLS 1.2, which is the version this RFC governs. |
| RFC 5282 | IKEv2 AEAD algorithms | Supported | 19 gated: 15 proven, 0 annotated, 4 untested | AES-GCM IKEv2 AEAD encryption and decryption framing: the eight-octet IV, the salt-then-IV twelve-octet nonce, the full-length sixteen-octet ICV, the associated data from the fixed header through the Encrypted payload's own generic header, any Padding up to 255 octets on receipt, the KEYMAT layout of SK_ei and SK_er, and the negotiation rules that carry the Key Length attribute and refuse an integrity transform. | AES CCM is not implemented. specifiedEncryption (crypto/proposal.go) accepts no CCM transform off the wire and aeadSaltBytes (crypto/transform.go) holds one entry, so the AES CCM rows of the checklist name behavior no layer performs and carry no test. Whether they are conformance debt or an optional feature ze declined is an owner answer, not this summary's. |
DNS, provisioning, MRT, and flow telemetry
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 1035 | DNS message behavior | ∿ | 27 gated: 25 proven, 0 annotated, 2 untested | 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 (Authoritative in). A reply on a datagram transport is bounded and carries TC when it is shortened (send and udpReplyLimit, same file): the Section 2.3.4 floor of 512 octets, raised to the reassembly buffer an RFC 6891 Section 6.2.3 OPT record advertises. A stream reply is written whole. An opcode Ze does not serve draws Not Implemented before any zone lookup, which is the Section 6.4 reply an unsupported inverse query gets (Authoritative, same file). 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 the surviving obligation with no code path is zone transfer: Ze performs none, and the owner ruled RFC 1035 out of scope on 2026-08-18, so that work is not to be started. A response TTL is deliberately not raised to the zone SOA MINIMUM. RFC 2308 Section 4 withdrew that rule ("the minimum TTL value of all RRs in a zone, has never in practice been used and is hereby deprecated"), hdr in applies no floor, and TestRFC2308_NoZoneWideTTLFloor holds the decision. The DNS wire codec is, so several encoding obligations admit no negative test through Ze at all. Declared backlog while an owner ruling is outstanding. internal/plugins/geodns/server.gogithub.com/miekg/dns |
| RFC 2181 | DNS clarifications | ∿ | 23 gated: 1 proven, 22 annotated, 0 untested | 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) | ∿ | 16 gated: 5 proven, 11 annotated, 0 untested | 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) | ∿ | 19 gated: 4 proven, 15 annotated, 0 untested | 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 | ∿ | 38 gated: 0 proven, 38 annotated, 0 untested | 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 | ∿ | 108 gated: 5 proven, 103 annotated, 0 untested | 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 | 3 gated: 2 proven, 1 annotated, 0 untested | Authoritative sink for misdirected RFC 1918 and link-local reverse DNS. | ✕ |
| RFC 7535 | EMPTY.AS112.ARPA | ∿ | 1 gated: 0 proven, 1 annotated, 0 untested | EMPTY.AS112.ARPA DNAME-redirection zone. | ✕ |
| RFC 2131 | DHCPv4 | ∿ | 64 gated: 15 proven, 49 annotated, 0 untested | 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 | ∿ | 34 gated: 0 proven, 34 annotated, 0 untested | 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 | 5 gated: 1 proven, 4 annotated, 0 untested | PXE boot option injection for BIOS and UEFI bootfile selection. | ✕ |
| RFC 1350 | TFTP | Supported | 11 gated: 1 proven, 10 annotated, 0 untested | 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 | 4 gated: 2 proven, 2 annotated, 0 untested | Option negotiation for TFTP provisioning paths. | ✕ |
| RFC 6396 | MRT | Supported | 13 gated: 1 proven, 12 annotated, 0 untested | 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 | ∿ | 6 gated: 4 proven, 2 annotated, 0 untested | 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 | 9 gated: 2 proven, 7 annotated, 0 untested | 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 | 19 gated: 4 proven, 15 annotated, 0 untested | 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 | Receiver-advertised per-family path-count requests for ADD-PATH, with session-wide outbound enforcement across normal sends and forwarding, including the route-server fast path. The configuration knob states a request to the peer and does not bound what Ze accepts: a peer that ignores it is not policed. |
| 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 | ∿ | Capability declaration only. extractLLNHCapabilities advertises the empty code 77 capability for a peer or group whose config carries a link-local-nexthop key that is not disable. The draft's own procedures are the 16-octet Link-Local-ONLY Next Hop form, and ze produces neither side of it. Send: linkScope.linkLocalNextHop and applyLinkLocalNextHop emit the RFC 2545 forms only, a 16-octet GLOBAL address or the 32-octet global-then-link-local pair, and attribute.ValidateGlobalNextHop refuses a link-local address in the first slot. Receive: parseNextHops performs no fe80::/10 test, so a 16-octet link-local-only Next Hop is read as a Global IPv6 next hop. parseCapability has no case for code 77, so ze records no negotiated state and no ze path is conditioned on the capability. The requirements are listed per line in and the walk is bounded by. internal/component/bgp/plugins/llnh/llnh.gointernal/component/bgp/reactor/link_scope.gointernal/core/bgp/attribute/nexthop_form.gointernal/core/bgp/attribute/mpnlri.gointernal/core/bgp/capability/capability.gorfc/short/draft-ietf-idr-linklocal-capability.mdrfc/extraction/draft-ietf-idr-linklocal-capability.json |
| draft-abraitis-bgp-version-capability | BGP Software Version capability code 75 | ∿ | Send side only, and the draft makes both halves optional: "Implementations are not required to advertise the version nor to process received advertisements" (Abstract). encodeValue writes one length octet plus the constant ZeVersion, and extractSoftverCapabilities (same file) declares code 75 only for a peer or group whose config carries a software-version capability key that is not disable or refuse, so the default is disabled. Receive side: parseCapability has no case for code 75, so a received capability becomes an Unknown that nothing reads. That is the RFC 5492 Section 3 outcome the draft points at, and it is why the three receiver obligations are met by ignoring rather than by parsing. ze bgp decode capability 75 <hex> decodes a payload an operator supplies; that path is a diagnostic tool and is not on the session receive path. Carried in this table as draft-ietf-idr-software-version until 2026-09-01. No IETF document of that name exists: the datatracker knows only draft-abraitis-bgp-version-capability, revision 18, "Software Version Capability for BGP", which IANA names as the reference for code 75. had always spelled the real one. internal/component/bgp/plugins/softver/softver.gointernal/core/bgp/capability/capability.godocs/architecture/wire/capabilities.md |
| draft-walton-bgp-hostname-capability | BGP FQDN capability code 73 | Supported | The draft states no MUST-level obligation: its only RFC 2119 keyword outside the Section 2 key-words paragraph is one SHOULD in Section 4, which is why signs off under manual-walk. Capability code 73, decode support, and per-peer hostname and domain advertisement (FQDN). Carried as RFC 8516 in the table above until 2026-08-30; RFC 8516 is the CoAP "Too Many Requests" response code and has nothing to do with BGP. IANA names this draft as the reference for code 73, and the scoped config keys the capability emits had always spelled it. rfc/extraction/draft-walton-bgp-hostname-capability.jsoninternal/core/bgp/capability/capability.go |
| 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 |
| draft-ietf-idr-bgp-bfd-strict-mode | BFD strict mode for BGP, capability code 74 | Supported | Capability code 74, length 0, advertised in the OPEN for a peer whose connection bfd { strict true } is enabled, and negotiated to Negotiated.BFDStrictMode when both speakers send it. FSM events 30 to 35 and the two OpenSent sub-states of Section 8.1 are implemented in. The KEEPALIVE is withheld and the session held in OpenSent by Session.advanceAfterOpen, released by Session.handleBFDEvent, and closed with Cease / BFD Down or Cease / Other Configuration Change by the same function. The BFD session opens before the BGP FSM starts and outlives a transition to Idle (Peer.run, Peer.cleanup, Section 7). The BfdHoldTimer of Section 3 attribute 18 lives on fsm.Timers, defaults to 30 seconds, and is armed only when the negotiated BGP hold time is zero; where it is non-zero the ordinary RFC 4271 HoldTimer bounds the wait, re-armed to the negotiated value by advanceAfterOpen. The Section 10 BFD hold-down interval is the hold-down leaf, in milliseconds, zero by default. Both halves are proven against a second implementation: test/interop/scenarios/bgp-bfd-strict-speaker (the lab speaker, which advertises capability 74 and answers BFD built from RFC 5880) and test/interop/scenarios/bgp-bfd-strict-frr (FRR 10.3.1, which implements neither). Sections 8.3.5 and 8.4.5 revise Event 20, an OPEN received while the DelayOpenTimer runs; Ze implements no DelayOpenTimer, an RFC 4271 optional session attribute its Section 8.2.1.3 permits omitting, so ConnectDelayOpenBfdUpPending and ActiveDelayOpenBfdUpPending are unreachable and undeclared. Neither section carries a MUST-level obligation, so that is an implementation gap in RFC 4271's optional feature and not a conformance gap in this draft. internal/component/bgp/fsm/internal/component/bgp/reactor/session_bfd_strict.go |
| 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). |