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 36 of 201 summaries (17.9%) 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 | ∿ | 125 gated: 100 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 an attribute ze does recognize keeps the bit the sender chose and a bit an earlier AS set on an optional transitive attribute is never cleared; the Section 4.3 companion rule on the same octet, which clears the Partial bit on a well-known attribute and on an optional non-transitive one at the same site (RFC4271-4.3-2, ClearPartialOnWellKnownAndNonTransitive, internal/core/bgp/attribute/partial.go), because RFC 7606 Section 3(c) accepts such an octet on receipt and the route-server relay then copies it onward byte for byte; 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. The 2026-09-21 extraction walk added twenty-four rows this checklist had never carried, and none of them is tagged yet: the six OPEN Error Subcode rules of Section 6.2 (RFC4271-6.2-5 to 6.2-10), the fourteen UPDATE Error Subcode and Data-field rules of Section 6.3 (RFC4271-6.3-4 to 6.3-17), the Section 5 pass-along rule for an updated well-known attribute (RFC4271-5-8), the Section 8.2.2 tracking of a second connection until it sends an OPEN (RFC4271-8.2.2-19), the Section 9 placement of a new route in the Adj-RIB-In (RFC4271-9-4) and the Section 10 jitter factor (RFC4271-10-4). Whether ze produces each behavior is unassessed; the rows record the obligation, not a verdict. 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: 4 proven, 5 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 | ∿ | 53 gated: 46 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, §3.c attribute-flags validation for every attribute whose own specification fixes an Optional or Transitive value (which also carries the two attributes whose RFC mandates other handling: the MP pair and AIGP), 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. internal/core/bgp/attribute/flags_spec.gorfc/requirements/rfc7606.md |
One MUST-level gap, annotated in and gated by ./le rfc check: §5.1 first bullet: Ze emits MP_UNREACH first and MP_REACH in type-code order after ORIGIN and AS_PATH, so the MP attribute of an announcement is not the very first path attribute (docs/architecture/wire/mp-nlri-ordering.md). TestRFC7606Section51MPAttributeEncodedFirst demonstrates the gap and fails the day it closes. 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 | 2 gated: 1 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 7705 | AS migration: Local AS, No Prepend Inbound, Replace Old AS, Internal BGP AS Migration | Supported | 9 gated: 9 proven, 0 annotated, 0 untested | All four mechanisms. Configuration is one session > asn container read by parsePeerSettings (internal/component/bgp/reactor/config.go) out of the peer-fields grouping that a group and a peer both use, so local, local-options and migration are each settable per neighbor and per neighbor group (RFC7705-3.3-1, RFC7705-4.2-1). Section 3.3: local-options no-prepend keeps the Local AS value out of the AS_PATH of a route received from that peer, and leaves the globally configured AS number on the path every other external neighbor receives (RFC7705-3.3-2, RFC7705-3.3-3); local-options replace-as sends the Local AS value alone toward that peer, with the globally configured AS number absent from AS_PATH and from AS4_PATH (RFC7705-3.3-4, RFC7705-3.3-5). Section 4.2: session > asn > migration widens one iBGP session to exactly two of this speaker's AS numbers. An OPEN carrying either is accepted and any third AS is answered with OPEN Message Error / Bad Peer AS, which ze originates for the first time (validateOpenPeerAS, internal/component/bgp/reactor/session_open_as.go, RFC7705-4.2-2). The OPEN ze sends carries the globally configured AS number first and the locally configured one after a peer answers Bad Peer AS, in My Autonomous System and in the Four-octet AS capability alike (openLocalAS, RFC7705-4.2-3, and the Section 4.2 SHOULD RFC7705-4.2-5 through noteASMigrationRejection). The session is internal under either AS number, from one rule every AS-scoped decision reads (isIBGPWith, RFC7705-4.2-4), so no external AS_PATH prepend runs and the RFC 4456 reflection rules apply. Requirements bound per line below. |
Two advisory items, neither of them a MUST. RFC7705-3.3-9, the Section 3.3 SHOULD that the Local AS value be appended inbound before a route is installed or advertised to an internal neighbor, is not performed by the engine: an operator writes it as policy, and plan/spec-bgp-local-as-inbound-append.md holds the ruling. RFC7705-3.3-11, the MAY letting an EXTERNAL speaker attempt the session under either AS number, is not offered: the two-AS session is iBGP only, because setMigrationAS refuses a remote AS that is neither of the pair. RFC7705-3.3-8, the SHOULD NOT that follows from that MAY, is therefore met by the scope decision rather than by a branch: no external session of ze establishes on the globally configured AS number while a Local AS is set for it. |
| 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: 4 proven, 0 annotated, 0 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). The related ICMP error messages of a BGP GTSM peer are carried by internal/component/gtsm, which the reactor's peer reconcile publishes the peer set to (Reactor.gtsmPeers, internal/component/bgp/reactor/gtsm.go): a host route carrying RTAX_HOPLIMIT 255 for the transmit rule, and the ze_gtsm nftables input table for the IPv4 receive rule. 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. The related-message filter claims a BGP GTSM session: it associates an ICMP error with a session by the header the error quotes alone, the quoted IPv4 destination (the peer) and the quoted TCP port on either side (peerTerms, internal/component/gtsm/gtsm.go, and lowerICMPErrorQuotedDestinationMatch, internal/plugins/firewall/nft/lower_linux.go), reads no outer source, and drops the error below the floor, so the related messages of a BFD or VRRP GTSM session, which quote no TCP header, are not claimed by it. The drop policy for a Dangerous related message is not configurable; ze applies the answer it already applies to a Dangerous main packet. Owner ruling, 2026-09-15: the Section 3 sentence "are expected to be configurable" is a suggestion outside the RFC 2119 vocabulary and not a requirement, so the drop stays fixed and no option is added. 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 | 5 gated: 2 proven, 3 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 | 4 gated: 4 proven, 0 annotated, 0 untested | ORIGINATOR_ID, CLUSTER_LIST, route-reflector plugin behavior, cluster checks. | ✕ |
| RFC 7311 | AIGP attribute | ∿ | 22 gated: 22 proven, 0 annotated, 0 untested | Attribute codec and presentation; receive validation; per-session enablement; configured origination; next-hop-self accumulation; retained-generation re-advertisement; native and forked best-path reselection. | Automatic origination policies are not enabled. Origination accepts explicitly configured metrics and remains subject to the domain and next-hop restrictions in Section 3.4.1. |
| 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 | ∿ | 18 gated: 0 proven, 18 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 | ∿ | 102 gated: 10 proven, 92 annotated, 0 untested | EVPN NLRI family (AFI 25 / SAFI 70), route-type codecs, ADD-PATH framing, MP_REACH construction and explicit configured control-plane origination through evpn.parseConfigRoute. The shared message.ValidateEVPNOrigination guard requires a Type 1 RD and ES-Import Route Target for locally originated Ethernet Segment routes. These source paths await current integration verification; permissive receive decoding is not evidence of sender compliance. |
Thirteen MUST rows carry {gap}. No full EVPN PE or multihoming claim: MAC-VRF/EVI ownership, automatic segment discovery, DF election, MAC learning, EVPN forwarding, split horizon, aliasing and BUM replication remain outside the implemented control-plane boundary. The checklist retains the separate requirement gaps; its historical counts and source citations are not current execution evidence. The new Type 4 origination guard and its behavioral carrier require Main's verification. Route targets are never auto-derived from the Ethernet Tag ID (Section 7.10.1). |
| 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 | ∿ | 30 gated: 22 proven, 8 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 |
The native codec, prefix-policy projection, unicast-authorized selection and revalidation, and selected-rule firewall path have source changes and regression carriers awaiting integration validation. No conformance result follows from these edits alone. Kernel precedence, continuation, sampling, policing, marking, and withdrawal require the Linux integration carrier with network-namespace and nftables privileges. VPN FlowSpec is validated and propagated by the BGP RIB but is not installed by the global firewall bridge. The rule-a destination bypass remains absent. |
| RFC 8956 | IPv6 FlowSpec | ∿ | 9 gated: 7 proven, 2 annotated, 0 untested | AFI 2 / SAFI 133 and 134 capability negotiation and native NLRI handling; offset-aware prefix patterns with checked bounds and padding normalization; family-specific fragment reserved-bit masking and single-octet encoding; destination-offset-zero validation against the matching IPv6 unicast or VPN RIB. Source and regression carriers are present; integration validation remains outstanding. | Linux firewall packet verification requires the privileged integration carrier. The global firewall bridge refuses unsupported predicates, including nonzero source offsets, fragments and Flow Labels, rather than installing a broader filter. VPN FlowSpec is validated and propagated but not installed in the global firewall. These disclosures are not a conformance or coverage-count claim. |
| RFC 5575 | BGP Flowspec (obsoleted by RFC 8955) | ∿ | 8 gated: 5 proven, 3 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. | RFC 8955 and RFC 9117 supply the current validation rules. The native codec, retained-route feasibility and revalidation, first-AS comparison against covering unicast reachability, originator/local-controller authorization, and more-specific-AS guard have source changes and regression carriers awaiting integration validation. The global firewall installs selected SAFI 133 rules only; VPN FlowSpec remains a BGP propagation feature. |
| 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) | ∿ | 59 gated: 45 proven, 14 annotated, 0 untested | Consumer decoding and syntactic propagation remain separate from native origination. bgp-ls-export consumes registered IS-IS, OSPF, and EPE snapshots, emits distinct topology identities, enforces opaque LSA provenance, clears originated reserved flags, reconciles withdrawals and replacements, and supports per-domain 64-bit Instance-ID configuration. Native SPF-unreachable origins are withdrawn and re-advertised on native SPF recovery, including unchanged LSDB objects; reachable-origin half-links remain advertised. Native EPE index advertisements require a matching advertised SRGB. |
Two MUST rows carry {gap}. RFC9552-5.3-2 still requires oversized propagation to discard the BGP-LS Attribute first. The owner selected Standard origination only: encodeTopology emits standard NLRI types and originateAttributes refuses private-use TLV types; no native vendor-private producer is enabled. Section 5.4's Enterprise Code duties are conditional on private origination; received unknown/private NLRIs remain opaque on the propagation path. New native origination proofs require centralized execution and discrimination; no conformance claim follows from the source additions alone. The native exporter does not bound a BGP-LS Attribute by the collector session's maximum UPDATE size (RFC9552-5.3-1). |
| RFC 9085 | BGP-LS Segment Routing extensions | ∿ | 11 gated: 0 proven, 11 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. | Eight origination/encode MUSTs unmet (decode-only plugin, no config surface): the reserved-and-flags-zero-on-transmit rules have dormant encoders but no origination path, and the LAN-Adjacency-SID (TLV 1100) and Range (TLV 1159) TLVs are not implemented at all. The TLV-placement rule RFC9085-2.1-1 is unmet too and is a SHOULD, the level the document states it at. |
| RFC 9514 | BGP-LS SRv6 extensions | ∿ | 20 gated: 15 proven, 5 annotated, 0 untested | The offline consumer decodes SRv6 Capabilities, Locator, End.X SID, Endpoint Behavior, BGP PeerNode SID, and SID Structure TLVs. The native IS-IS LSDB adapter maps SRv6 capabilities, locators, End/End.X SIDs, and SID structure into BGP-LS advertisements with source topology and locator association. The originator clears reserved words, requires Endpoint Behavior for SID NLRIs, and refuses oversized SID structures. | Native OSPFv3 SRv6 state and SRv6 BGP EPE segment assignment are absent. SID Structure sum-at-most-128 validation is absent from the offline consumer decoder. New native producer proofs still require centralized execution and discrimination. |
| RFC 9086 | BGP-LS Egress Peer Engineering SIDs | ∿ | 11 gated: 8 proven, 3 annotated, 0 untested | PeerNode/Adj/Set SID TLVs and BGP Router-ID/Member-ASN descriptors decode. The native bgp-epe producer installs configured PeerNode labels through the MPLS FIB owner, waits for installation acknowledgement, and publishes live session identities with its SRGB. bgp-ls-export advertises that state and withdraws replaced or removed identities. |
PeerAdj and PeerSet segment assignment are not implemented. Native EPE uses persistent operator-assigned indices, not three-octet label assignments. Centralized runtime verification and discrimination of the new producer remain required. |
| RFC 4684 | Route Target Constraint | ∿ | 6 gated: 2 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). Two further MUST rows the 2026-09-21 extraction walk added carry no test: RFC4684-4-1 (Next Hop length decides IPv4 or IPv6, §4) and RFC4684-5-2 (advertise the AFI/SAFI pair with the Multiprotocol Extensions capability, §5). 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/core/bgp/attribute/prefixsid.go) 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) | ∿ | 25 gated: 12 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/core/bgp/attribute/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). Six further MUST gaps were added by the 2026-09-21 extraction walk and none is proven: RFC9252-3.2.1-4, RFC9252-3.2.1-5, RFC9252-3.2.1-6, RFC9252-5-3, RFC9252-7-1 and RFC9252-7-2, so the count of MUST gaps on this page is fourteen, not eight. |
| 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 | ∿ | 32 gated: 26 proven, 6 annotated, 0 untested | BMP receiver and sender, peer lifecycle, current per-NLRI replay with per-family End-of-RIB, received and sent route monitoring, CLI and configuration. Initiation carries the administrative system identity; receiver information strings retain order and duplicates. Sender shutdown carries Reason 0 and closes; bounded transmit queues reset stalled sessions. | Focused execution, producer discrimination and third-party pmacct acceptance of the current changes remain unrun. Loc-RIB monitoring is defined under RFC 9069 and Adj-RIB-Out under RFC 8671. |
| 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 | ∿ | 60 gated: 25 proven, 35 annotated, 0 untested | Ze implements the RTR router client, not a cache server. It polls caches in preference order, replaces complete VRP and ASPA sets at End of Data, and expires those sets independently of connection progress. Native mutual TLS uses a named PKI CA and client identity, a DNS-ID cache reference, and no CN or plaintext fallback. Unprotected TCP requires an explicit trusted-network selection. Candidate PKI is isolated from live configuration; transport changes retain data only for its original lease. Producers: internal/component/bgp/plugins/rpki/{rtr_session.go,rtr_tls.go,rtr_expire.go,rpki_config_verify.go,rpki_reload.go}. | Five MUST rows carry {gap}. Session ID mismatch handling, received supported-version mismatches, and Router Key handling remain incomplete. The requirement rows below retain the specific remaining scope and verification obligations. Cache-server-only obligations do not describe the router's local staging or validation behavior. |
| RFC 6810 | RPKI-to-Router Protocol v0 (RPKI-RTR) | ∿ | 40 gated: 6 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 | 16 gated: 0 proven, 0 annotated, 16 untested | Ze consumes validated ROA payloads through RTR. The signed ROA object, EE certificate extensions, signature verification and certification-path validation belong to the configured relying-party cache. parsePrefixPDU decodes only the RTR prefix, maximum length and origin ASN. |
The RFC 9582 signed-object profile is performed by the relying-party cache. Historical claims of RTR v2 support under this RFC number were misattributed; current RTR requirements are described in draft-ietf-sidrops-8210bis. |
| RFC 5880 | BFD base | ∿ | 98 gated: 78 proven, 20 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 |
Twelve 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); a reduced bfd.RequiredMinRxInterval enters the Detection Time at once instead of at Poll termination (RFC5880-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); 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 | ∿ | 7 gated: 7 proven, 0 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. Since 2026-09-20 the record also covers the case Section 4 is written for. sendNotificationWithin (internal/component/bgp/reactor/session_write.go) calls onNotifSent after every write attempt, delivered or not, and Peer.recordNotificationSend routes a refused write to recordNotificationUnsent, which stores the code and subcode under direction send-failed. last-error then reads "Cease/BFD Down (not sent)" and show bgp peer <address> carries last-notification.direction, so an operator reads the reason and can tell it from a reason the peer was told. RFC9384-4-1 is proven in both polarities: TestNotificationRefusedBySocketStillRecordsTheReason and TestNotificationDeliveredIsNotRecordedAsUnsent (internal/component/bgp/reactor/peer_last_error_test.go) and TestBgpSummaryLastErrorSeparatesToldFromCouldNotTell (internal/component/bgp/plugins/cmd/peer/last_error_unsent_test.go). rfc/extraction/rfc9384.jsonrfc/short/rfc9384.md |
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 | 38 gated: 28 proven, 10 annotated, 0 untested | Native IS-IS over Layer 2, L1/L2 broadcast and point-to-point circuits, IPv4 interface advertisement, node-wide protocol capability, narrow IPv4 SPF and leaking, and terminal forwarding rejection for unsupported protocol families. sends and receives ISO 9542 discovery; ish_auth.go applies configured cleartext link passwords before discovery changes state. IS-IS Hello, area and domain authentication use their scoped key chains. This describes source coverage, not a completed validation run. circuit/ish.go |
Experimental pending current integration, discrimination and deployment evidence. Native IDRPI origination, manually configured summaries, extended-fragment operation and TE path computation remain outside the selected boundary. ISH discovery does not replace authenticated IIH adjacency establishment. TOS/QOS metrics and partition repair remain outside scope; Section 3.5 makes TOS/QOS optional. |
| 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 | 11 gated: 7 proven, 4 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 | 5 gated: 5 proven, 0 annotated, 0 untested | Up/down bit retention and redistribution behavior. | Same IS-IS experimental status. RFC2966-3.2-1, the Section 3.2 route preference order, was added by the 2026-09-21 extraction walk and carries no test. |
| RFC 5308 | IS-IS IPv6 | Experimental | 8 gated: 8 proven, 0 annotated, 0 untested | IPv6 reachability over the same IS-IS instance. | Same IS-IS experimental status. RFC5308-2-3, the external bit in TLV 236 for a redistributed prefix, was added by the 2026-09-21 extraction walk and carries no test. |
| 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 1071 | Internet checksum (OSPF, VRRP, ICMP probe, RSVP-TE) | ∿ | 6 gated: 1 proven, 5 annotated, 0 untested | Ze computes the ones-complement 16-bit checksum in its own Go for the OSPF packet header and LSAs (PacketChecksum, FinalizeLSAChecksum), VRRP (FillChecksum), the ICMP probe (icmpChecksum) and RSVP-TE, and verifies received OSPF and VRRP packets (VerifyPacketChecksum, verifyChecksumSum). |
The verification rule (RFC1071-1-5) is audited wrong: its tagged tests check the generated checksum with a test helper and drive no Ze verify function, so receive-side checksum verification is implemented but not proven under this document. |
| RFC 2328 | OSPFv2 | ∿ | 52 gated: 50 proven, 2 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 |
The RFC2328-13.3-2 normal-flood InfTransDelay gap remains recorded below. The 2026-09-21 extraction added obligations that require current positive/negative coverage and discrimination. New IP-envelope, source-address, classless-route and virtual-link carriers have been added; current runner proof and deployment evidence remain pending. |
| RFC 3101 | OSPF NSSA | Experimental | 24 gated: 24 proven, 0 annotated, 0 untested | Type-7 origination/flooding, N/E-bit Hello negotiation, redistribution into NSSA, translator election and Type-7 to Type-5 translation, summary import policy and mandatory border-router defaults. The external stage retains per-area ASBR reachability and enforces Type-5/Type-7 forwarding-address eligibility. Per-source nssa-propagate defaults to false, and interface lifecycle reconciliation replaces forwarding addresses for retained imports. The shared external calculation and origination policies serve OSPFv2 and the RFC 5838 OSPFv3 address families. |
Current producer and tagged-test changes require the parent validation run and discrimination proofs before any revised conformance claim. The existing operator-path default scenarios and FRR scenario remain part of that verification population. |
| RFC 5709 | OSPFv2 HMAC-SHA authentication | Experimental | 14 gated: 12 proven, 2 annotated, 0 untested | OSPFv2 cryptographic authentication path. | Same OSPF experimental status. |
| RFC 7474 | OSPFv2 manual-key security extension | Experimental | 12 gated: 9 proven, 3 annotated, 0 untested | AuType-3 cryptographic authentication with extended sequence numbers, per-packet-type replay protection, source-bound Apad and protocol-ID key derivation. | Experimental. One MUST row carries {gap}. Section 8 requires authentication-key replacement after non-volatile storage loss or router replacement. An absent durable counter starts at one and cannot distinguish first use from total storage loss; startup does not automatically rotate the configured shared secrets. |
| 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 | 46 gated: 2 proven, 44 annotated, 0 untested | Ordinary OSPF: area and external routing, RFC 6549 instances, cryptographic authentication, backbone virtual links and plain unicast redistribution. These are not RFC 4577 VPN-PE import/export. | Native VPN-PE behavior is not selected: no OSPF-domain/VRF association, VPN metadata import/export, VPN Route Tag loop prevention or optional sham links. Ordinary routing and CE obligations remain mandatory and tested under their governing RFCs. |
| RFC 5250 | OSPFv2 opaque LSAs | Experimental | 13 gated: 11 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: 5 proven, 7 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 | 9 gated: 6 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 | ∿ | 48 gated: 29 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. The 2026-09-21 extraction walk added RFC8665-3.2-14, the reception half of the seven "Reserved: SHOULD be set to 0 on transmission and MUST be ignored on reception" field descriptions, which the checklist had carried only as the transmission-side SHOULD (RFC8665-3.2-9); that MUST carries no test yet and is unannotated, because an annotation on a row is the owner's to write. rfc/short/rfc8665.md |
| RFC 8666 | OSPFv3 Segment Routing | ∿ | 31 gated: 22 proven, 9 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 |
Two MUST gaps, annotated in and gated by ./le rfc check. RFC8666-5-3 and RFC8666-5-4: reception judges Prefix-SIDs per prefix, topology, algorithm and advertising router but never consults the carrying LSA's Instance ID (srPrefixSIDScope, internal/plugins/ospf/sr_malformed.go), 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. 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: 1 proven, 0 annotated, 37 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 Section 2 requires (fixed in a9d0b54387, 2026-09-20). 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 | 83 gated: 22 proven, 61 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. The 2026-09-21 walk widened the checklist to the whole document, so this list now covers a small part of the row set rather than most of it. | The 2026-09-21 extraction walk read all 87 normative sites in and grew the checklist from 19 rows to 86. Messages ze emits on one path only: RFC5036-2.5.3-2 and RFC5036-3.5.1-1 (a Notification NAKs an unacceptable Initialization, and every other fatal error still 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). The walk corrected four rows against the document: RFC5036-2.9-1 rose to MUST, RFC5036-2.6.1.1-1 fell to MAY, RFC5036-3.5.1-1 fell to SHOULD, and RFC5036-2.7-1 lost a prohibition on sending labeled packets before MPLS forwarding is enabled, which no sentence of RFC 5036 states. Ze still imposes labels without that check, at ProgramPush in internal/plugins/ldp/fib.go, and the walk removed the row that recorded it, so nothing on this ledger gates it now. The 67 rows the walk added carry no test: the Loop Detection procedures, the ATM and Frame Relay label and session parameters, the Downstream on Demand Label Request, Abort and Release procedures, the vendor-private U-bit configuration interfaces, and the session parameter negotiations. rfc/full/rfc5036.txt |
| RFC 5561 | LDP | ∿ | 17 gated: 1 proven, 16 annotated, 0 untested | None of the document's behavior exists yet. Ze speaks base LDP (RFC 5036) only: EncodeInit in internal/plugins/ldp/wire.go writes no Capability Parameter TLV, DecodeTLV recognizes no capability code point, and no Capability message (0x0202) encoder or decoder exists in internal/plugins/ldp/session.go. The row is here so the 17 gated MUSTs are disclosed rather than hidden behind an absent row. |
Every one of the 17 MUST-level rows is a {gap} scheduled by one of two specs. Capability advertisement (plan/spec-ldp-capability-advertisement.md) owes the send side: the Capability Parameter TLV with its U-bit, F-bit, S-bit and length rules (RFC5561-3-1, 3-2, 6-1, 6-3, 9-1, 9-2), the Capability message and the Dynamic Capability Announcement gate on it (RFC5561-4-1, 4-2, 9-3), and the IPv4 label distribution default (RFC5561-10-1). Capability status codes (plan/spec-ldp-capability-status-codes.md) owes the receive side: the Malformed TLV Value and Unsupported Capability status codes, the E-bit of 0 on that Notification, and the Returned TLVs TLV that carries the offending Capability Parameter or the unknown TLV (RFC5561-3-5, 6-2, 8-1, 8-2, 8-3, 8-4). |
| RFC 3209 | RSVP-TE | Experimental | 59 gated: 17 proven, 42 annotated, 0 untested | PATH and RESV signaling, ERO routing, bandwidth admission, SE-style make-before-break, soft-state refresh/expiry, teardown. | Strict-hop validation and native loose-hop next-hop expansion are implemented in routing.go; this is not CSPF. Complete requirement-level proof and independent RSVP-peer interoperability remain open. Non-RSVP-hop knowledge, Hello, ATM/Frame Relay label ranges, IPv6 LSP_TUNNEL objects, resource-affinity validation and per-sender policing remain absent. Existing planning records do not authorize adding them. The IPv4 SESSION test does not prove RFC3209-4.6.1-2. Path-MTU programming exists, but bounded exact-fit and oversized-DF evidence does not prove Section 2.6's full fragmentation algorithm. Kernel implementation and proof must be assessed separately; the Go RSVP producer alone cannot establish a Linux-wide absence. |
| RFC 2205 | RSVP base protocol | Experimental | 71 gated: 16 proven, 55 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, and a received Path, Resv, PathTear, PathErr or ResvConf dropped when it omits an object its Section 3.1 BNF writes unbracketed; tests bound per requirement in. rfc/requirements/rfc2205.md |
Forty-eight MUST rows carry {gap}. Checksum validation, supported FF/SE message checks, ResvErr/ResvTear, local address-based policy and native transport are implemented. Complete requirement-level proof remains open. Missing enforcement includes refresh synchronization avoidance, the lifetime floor, bounded period increases and non-RSVP-hop detection. Received-interface loop checks and reverse-interface retention also remain absent. IntServ host reservations, multicast/WF signaling, authenticated POLICY_DATA interpretation and UDP encapsulation are absent capabilities, not implementation authorization. The baseline records bounded native Ze-to-Ze evidence separately from full conformance and independent-peer interoperability. |
| RFC 4090 | RSVP-TE Fast Reroute | Experimental | 32 gated: 31 proven, 1 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. | PLRs now signal and refresh the protected PATH through the bypass, and an MP merges it into matching protected state (Sections 6.4.3, 6.4.4, 7.1.1). A head-end PLR uses a distinct assigned local sender address and a two-label ingress push (Section 6.1.1). Runtime and discrimination evidence must be regenerated for these producers. One-to-one detours remain absent; Section 6 states "A PLR MAY support the DETOUR object". A bandwidth-guaranteed bypass is also absent. Those optional feature records require a scope decision and do not authorize implementation. plan/spec-rsvpte-bypass-bandwidth-protection.md |
First-hop redundancy
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 9568 | VRRPv3 (IPv4 and IPv6) | ∿ | 59 gated: 40 proven, 19 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 | 40 gated: 32 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 3768 authentication types are deliberately not implemented: RFC 9568 Section 9 removed them as providing no real security. Same VRRP experimental status. |
| RFC 5798 | VRRPv3 (obsoleted by RFC 9568) | ∿ | 55 gated: 39 proven, 16 annotated, 0 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. 39 of those 55 are proven under RFC5798 ids in both polarities. 4 carry a positive test and a {single-polarity} annotation, because the producer writes a constant no input changes: RFC5798-5.1.1.3-1, RFC5798-5.1.2.3-1, RFC5798-7.2-2 and RFC5798-7.2-4. RFC5798-5.1.2.3-1 is the weakest of those four, because its only evidence is an integration-gated test the privileged QEMU suite runs and ./le rfc discriminate-record cannot reach. 3 are met below ze and carry a {lower-layer} annotation: RFC5798-5.1.1.2-1 and RFC5798-5.1.2.2-1, where Linux consumes a link-local scope multicast datagram rather than forwarding it, and RFC5798-7.4-1, where Linux derives each device's Interface Identifier. 2 are conditional on an optional feature ze declined and carry a {feature-declined} annotation: RFC5798-8.4.2-1, the VRRPv2/VRRPv3 dual-send flag RFC 5798 Section 8.4.2 makes a MAY, and RFC5798-A.2-1, the Token Ring functional-address mode. 7 are {gap} rows ze owes: RFC5798-6.4.3-4 and RFC5798-8.2.3-1 (no ND Router Advertisement is sent for a virtual router, so there is no option set to configure either), RFC5798-7.4-2 (the virtual-MAC macvlan carries no addr_gen_mode, so Linux derives its link-local Interface Identifier from the virtual router MAC), RFC5798-8.1.3-1 (a proxy ARP reply on a VRRP router does not carry the virtual router MAC), RFC5798-8.2.2-2 and RFC5798-8.2.2-3 (ze authors no Neighbor Solicitation for a host and installs nothing that sources one from the virtual MAC), and RFC5798-7.1-3, where 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) | ∿ | 30 gated: 15 proven, 15 annotated, 0 untested | fetchASMetadata requires HTTPS, an application/json object, an exact issuer match and an HTTPS jwks_uri. oauthHTTPClient prevents redirect downgrades. parseJWKSDocument rejects ambiguous key usage when encryption keys are present. The metadata URL uses the registered oauth-authorization-server suffix before the issuer path. |
Implementation features not offered under the 2026-09-21 scope decision: authorization/token/revocation/introspection endpoints and their client-authentication metadata, signed AS metadata, and an AS metadata publisher/TLS listener. These are absent features, not unimplemented obligations of the selected resource-server and plain-metadata-consumer roles. Applicable verification remains subject to the recorded tests and discrimination results. |
| RFC 9728 | OAuth 2.0 Protected Resource Metadata (MCP resource server) | ∿ | 27 gated: 13 proven, 14 annotated, 0 untested | Ze publishes plain JSON protected-resource metadata for its MCP resource server at the oauth-protected-resource well-known URL, answers GET and refuses other methods, keeps the escaped resource path and query in the resource identifier, and sends bearer challenges. |
Two rows are audited wrong. RFC9728-3.1-2 obliges the party that queries the metadata, a discovery-client role Ze does not fill under the 2026-09-21 scope decision, and its tags prove the publisher rule instead. RFC9728-3.1-3: resourceOriginAndPath trims the resource path's own trailing slash, which the RFC keeps, and the query-only case is not tested. Response signing and signed metadata are features not offered. |
| RFC 2661 | L2TPv2 | ∿ | 97 gated: 66 proven, 31 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). |
∿ |
| RFC 1661 | PPP LCP | ∿ | 75 gated: 45 proven, 30 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 |
∿ |
| RFC 1334 | PAP | ∿ | 13 gated: 13 proven, 0 annotated, 0 untested | PAP authentication option through PPP auth handling. | Verification remains for the retry and post-authentication reanswer changes in. The source batch adds tests for RFC1334-2.2.1-2, RFC1334-2.2.1-4, RFC1334-2.2.1-5, RFC1334-2.2-1 and RFC1334-2.3-3; no new test result or discrimination proof is claimed. plan/immediate/spec-pppoe-client-pap-retry.mdplan/immediate/spec-ppp-pap-reanswer-after-auth.md |
| 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 | ∿ | 7 gated: 5 proven, 2 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 row carries {gap}. Codes 8-11 received on IPCP are not Code-Rejected: the shared dispatcher maps them to LCP echo/protocol-reject handling. Codes 12 and above are Code-Rejected. RFC1332-2.1-3 is exercised at the backend MTU boundary: afterLCPOpen sets the IP MTU to the peer's negotiated Information-field MRU, without subtracting PPP framing; actual IP fragmentation belongs to the Linux IP stack and needs kernel-path verification. RFC1332-3.3-2 has tagged tests for the IP-Address value in a Configure-Nak. |
| RFC 1877 | IPCP DNS options | ∿ | ✕ | Primary and secondary DNS option parsing and negotiation: Ze offers its configured DNS addresses, Naks a request for them with its own values, and absorbs a peer Configure-Reject of the DNS options while still negotiating the IPv4 address. | Carries the L2TP and PPPoE Partial status. The NBNS options (130, 132) are not implemented. |
| 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 | ∿ | 37 gated: 23 proven, 14 annotated, 0 untested | Access concentrator (PADI/PADO/PADR/PADS/PADT discovery, AC-Cookie, session tables, AF_PPPOX kernel sessions) and PPPoE client/Host dialer share PPP codecs and option validation. The AC uses the PPP driver; the client runs its own negotiation loop. Requirement bindings are derived from the tagged tests. The 2026-09-21 PADT lifetime and prohibited-option regressions have been added but not run or discriminated during the concurrent source batch. | Four MUST rows carry {gap}. BuildPADI limits the PPPoE header and payload to 1484 octets, excluding the Ethernet header; the returned frame can reach 1498 octets and an oversized request returns nil. Tests exercise that boundary and the 16-octet relay-tag reservation. The Linux pppoe module writes session CODE 0x00. Both roles now stop PPP transport on PADT and reject ACCM, ACFC and FCS Alternatives (type 9); verification and discrimination for these changes remain owed. PFC is NOT RECOMMENDED rather than prohibited by Section 7. Remaining annotations below cover kernel resource-allocation evidence and the conditional relay-agent and Generic-Error features proposed in existing specs. Error tags currently carry no data. The session table reserves SESSION_ID 0xffff, and PADO/PADS emit Service-Name even when its value is empty. Receive-side tolerance of absent or repeated Service-Name is documented in docs/architecture/l2tp/bng-5-pppoe.md; Sections 5.1 and 5.3 bind the sender. No optional-feature decision is implied by the backlog specs. |
| RFC 2865 | RADIUS authentication | Supported for subscriber access | 29 gated: 24 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 | 15 gated: 15 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 | 8 gated: 7 proven, 1 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+ | ∿ | 56 gated: 44 proven, 12 annotated, 0 untested | PAP login followed by shell-session privilege authorization; printable-ASCII fields and UsernameCasePreserved usernames; exact decrypted lengths; ordered ERROR failover; mandatory command arguments; command accounting and negotiated single-connect reuse. | Current implementation changes await test and discrimination runs. On 2026-09-21 the owner selected tacacs_methods='Keep PAP authentication': no ASCII interactive, CHAP, MS-CHAP v1/v2 or ENABLE authentication workflows. The conditional method rows retain that selection and their source text. Section 10.5 also includes deployment obligations that a TCP reachability check cannot establish. |
IPsec, IKE, EAP, and kernel security associations
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 7296 | IKEv2 | ∿ | 223 gated: 223 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. Section 3.10.1 sender side: Ze adds one status notify of its own, the private-use ZE_PATH_PROBE_PADDING (65280), to the INFORMATIONAL request of the padded path probe that show mtu runs over a live SA; its Notification Data pads the datagram to a requested size, and a peer that does not recognize it ignores it and answers an empty response (engine/probe.go, wire/payload_notify.go). Section 3.14 is why SK padding is not that carrier: the Pad Length is one octet, so SK padding cannot pass 255 octets, and an AEAD suite pads nothing (RFC 5282); under CBC the padding rule fixes the encrypted span to the block size, so the probe reaches sizes on a 16-octet grid and refuses the rest by name. Section 2.1: the probe is an ordinary request; its retransmission is the same bytes from the IKE header on with only the IP header's DF bit changed, and a probe unanswered through the full budget deems the SA failed exactly as an unanswered Delete does (engine/established.go serviceRequestWindow, unchanged). 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 | ∿ | 78 gated: 34 proven, 44 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. The 2026-09-21 walk widened the checklist to the whole document, so this paragraph now describes the covered part of a much larger row set rather than most of it. |
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. Each of those blocks is now gated by a checklist row of its own, added by the 2026-09-21 extraction walk: RFC4301-6.1.1-1 and RFC4301-6.1.1-2 for the unauthenticated-ICMP control, RFC4301-6.2-1 for the ICMP error payload check, RFC4301-8.1-1 and RFC4301-8.1-2 for the DF bit, RFC4301-8.2.2-1 for PMTU ageing, RFC4301-4.4.2.1-2 for the byte-count lifetime, and RFC4301-5.1.2.1-1 for the outer-header DSCP mapping. RFC4301-4.4.1.1-1 remains the gated selector row and its reason text is stale about ports. The same walk added 49 further rows, for obligations no earlier pass had recorded at all: the Section 3 connectivity and ESP-support rows, the Section 4.4.1 SPD management interface and decorrelation rows, the Section 4.4.1.1 OPAQUE and ICMP-range selector rows, the Section 4.4.2.1 SAD data items, the Section 4.4.3 PAD rows, the Section 4.5.2 key-splitting rule, the Section 5 processing-step rows, and the Section 7 fragment-handling rows. None of them carried a test on 2026-09-21. The ruling pass of that day implemented the Section 5 catch-all (vpn ipsec unmatched, RFC4301-5-1), proved the Section 3.1, 4, 4.1, 4.4, 4.4.1, 4.4.1.1, 4.4.2.1, 5.1, 5.2 and 7.3 obligations the kernel performs on state Ze installs at the XFRM boundary, and scheduled every remaining MUST as a {gap} naming its spec: the Section 6 ICMP block, the Section 8 DF bit and PMTU block, the outer-header DSCP mapping and the ICMP type/code selector in; the byte-count lifetime in; OPAQUE ports in the operator list in; multiple SPDs, the user override control and the name selector in; the dual-family PAD range in; and NON_FIRST_FRAGMENTS_ALSO in. rfc/full/rfc4301.txtplan/immediate/spec-rfc4301-architecture-gaps.mdplan/spec-ipsec-lifetime-volume.mdplan/immediate/spec-rfc4301-architecture-gaps.mdplan/spec-ipsec-lifetime-volume.mdplan/spec-ipsec-opaque-selector-port-mask.mdplan/spec-ipsec-spd-selection.mdplan/spec-ipsec-pad-dual-family-range.mdplan/spec-ipsec-non-first-fragments-also.md |
| RFC 4302 | Authentication Header | ∿ | 32 gated: 9 proven, 23 annotated, 0 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 | ∿ | 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 | ∿ | 11 gated: 6 proven, 5 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 reloadPool stores the pool it builds in a field no code reads. No engine code constructs a Configuration payload, so ze sends no CFG_REPLY. Closing it is. internal/core/eap/pool.gointernal/component/ike/engine/apply.goplan/immediate/spec-ike-virtual-ip-assignment.md |
| RFC 4555 | MOBIKE | ∿ | 13 gated: 13 proven, 0 annotated, 0 untested | Tunnel-mode negotiation; UDP 4500 source selection; explicit address updates; COOKIE2 generation, echo, comparison and failure teardown; NO_NATS_ALLOWED sending and full receiver validation under the per-peer NAT policy; NAT detection; sequence-preserving live XFRM migration when the kernel supports XFRM_MSG_MIGRATE_STATE. Newly authored tests remain unrun. | Atomic migration requires Linux 7.2's API and CONFIG_XFRM_MIGRATE in the appliance build. Runtime verification, discrimination and interoperability results remain outstanding. |
| RFC 3748 | EAP | Supported in IPsec | 60 gated: 49 proven, 11 annotated, 0 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 |
Every MUST is met. Both key-deriving methods now export the two keys RFC 3748 Section 7.10 requires. EAP-TLS: exportEAPTLSKeys asks the TLS exporter for the whole 128-octet Key_Material both RFC 5216 Section 2.3 and RFC 9190 Section 2.3 define, and cuts it into MSK = Key_Material(0,63) and EMSK = Key_Material(64,127) on both roles. EAP-MSCHAPv2: deriveEMSK expands the RFC 3079 Section 3 MPPE master key into 64 octets with HKDF-Expand over SHA-256 under a label of its own, beside the DeriveMSK that already took the same root down the MPPE branch. No document defines an EAP-MSCHAPv2 EMSK, and none needs to: Section 7.10 says "The EMSK is not shared with the authenticator or any other third party" and Section 7.2.1 says "Use of the EMSK is reserved", so the key never reaches the wire and no interop rests on its value. The two keys are siblings of the master key rather than parent and child, which is what meets the separation Section 7.10 demands: "an attacker recovering the MSK or EMSK MUST NOT be able to recover the other quantity with a level of effort less than brute force." Reaching either from the other means inverting SHA-1 or HMAC-SHA-256 to get back to the master key. The EMSK then goes no further than the two sessions that derived it, as the same section requires: on both methods it is an unexported field of Session and of PeerSession with no accessor and no result field, so no other package can read it, and Close erases it. 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. internal/core/eap/eap_tls.gointernal/core/eap/mschapv2.go |
| RFC 2759 | MS-CHAPv2 (PPP and EAP inside IKEv2) | Supported | 11 gated: 8 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) | ∿ | 47 gated: 31 proven, 16 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. |
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). The 15 rows the 2026-09-21 extraction walk added were classified the same day under the owner ruling that every MUST is a requirement. Five TLS handshake obligations crypto/tls performs on the tls.Config Ze installs (the resumption ciphersuite, the server_certificate flight, the peer's full and resumed flights, and the server's resumed flight) are proven at that boundary in rfc5216_tls_config_test.go; the server_key_exchange (RFC5216-2.1.1-7) is decided by the crypto/tls default suites and carries a lower-layer annotation. RFC5216-2.1.1-4 is audited wrong: the server does not choose a sessionId for a new session, because crypto/tls, as newTLSMethod configures it, sends an empty ServerHello session_id on a TLS 1.2 full handshake, and its tags prove the session-ticket rule instead. Seven are scheduled gaps: TLS 1.0 support (RFC5216-2.4-7, which RFC 8996 forbids, plan/spec-eap-tls-tls10.md, the owner decides which document governs); the Peer-Id and Server-Id derivation and authorization (RFC5216-5.2-1, RFC5216-5.3-2, RFC5216-5.3-3, plan/immediate/spec-eap-tls-identity-authorization.md); and the certificate naming profile on issuers and verifiers (RFC5216-5.2-2, RFC5216-5.2-3, RFC5216-5.2-4, plan/spec-eap-tls-certificate-profile.md). RFC5216-5.2-5 is structural in crypto/x509 and carries a lower-layer annotation. The 12 rows the same walk added that are now proven in both polarities are the Section 2.1.1 Start and first Response, the six Section 2.1.5 fragment-ACK and Identifier obligations, and the four Section 3 packet-field obligations (rfc5216_flight_rules_test.go and rfc5216_fragment_ack_test.go, internal/core/eap). 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: 19 proven, 0 annotated, 0 untested | AES GCM and AES CCM IKEv2 AEAD encryption and decryption framing: the eight-octet IV, the salt-then-IV nonce at twelve octets for AES GCM and eleven for AES CCM, the full-length sixteen-octet AES GCM ICV and the eight, twelve and sixteen octet AES CCM ICVs, 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 with each cipher's own salt length, and the negotiation rules that carry the Key Length attribute and refuse an integrity transform. | The AES CCM transforms are offered for the IKE SA alone. An esp-group proposal naming one is refused at config parse, because xfrmAEADName (dataplane/xfrm_linux.go) names no kernel AES CCM transform and aeadSaltBytes (dataplane/vpp.go) knows no AES CCM salt. That is an implementation gap in the ESP dataplane, which RFC 4309 governs, and not a conformance gap in this document: every obligation RFC 5282 places on the IKEv2 Encrypted payload is met for both AEAD families. |
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 | ∿ | 65 gated: 16 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 | 14 gated: 2 proven, 12 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, so the v9 collector requirements x-4 to x-7 are marked not applicable and the five collector obligations added on 2026-09-21 (x-22 to x-26) are marked not applicable too. |
| RFC 7011 | IPFIX | Experimental | 50 gated: 11 proven, 39 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 |
[RFC7011-10-1]: the exporter transmits over UDP only and implements no SCTP transport, so the mandatory SCTP support is absent. [RFC7011-11.1-2]: the exporter offers no DTLS for UDP export, which RFC 7011 Section 11.1 makes mandatory. [RFC7011-11.1-3] [RFC7011-11-1] [RFC7011-11.3-1] [RFC7011-11.3-2] [RFC7011-11.3-3] [RFC7011-11.3-4]: with no DTLS, the exporter has no DTLS over SCTP, no mutual authentication, no certificate verification and no ciphersuite, and it exports to unverified collectors. [RFC7011-10.3.2-1]: UDP is the only transport, so an application that cannot tolerate loss has no alternative. Options Templates, reduced-size encoding and TCP transport are optional features the exporter does not implement. ze is an exporter only, and the IPFIX Collecting Process requirements bind a role it does not implement: holds encoders and one connected UDP sender (net.DialUDP) and opens no listening socket. internal/plugins/flowexport/sender.go:81 |
Configuration modeling and management protocols
| RFC | Area | Status | Proof | Implemented coverage | Remaining if not complete |
|---|---|---|---|---|---|
| RFC 7950 | YANG 1.1 data modeling | ∿ | 76 gated: 22 proven, 54 annotated, 0 untested | Config schema loaded from Ze's own YANG modules through goyang; value validation for string (length, pattern), the integer types (range, with negative bounds), decimal64 (lexical form, fraction-digits, range), boolean, enumeration and union; mandatory leaves; min-elements and max-elements; leaf and typedef defaults validated against their type at schema build; the loader refuses a bad escape, an unprefixed external reference, a leaf or typedef without a type, an extension with a non-YANG substatement, a descending or non-numeric range, a negative or descending length, a decimal64 without fraction-digits, and an enum value that is duplicated, beyond int32 or missing after the maximum. | Forty-eight MUST rows carry {gap}, each naming its spec. Ze's loader adds no check of its own where goyang accepts a violating module (circular imports, duplicate typedefs and groupings, a shared import prefix, a typedef named after a built-in type, a shared child name across choice cases, an action or notification under a keyless list, an augment adding a duplicate, an overlapping range, an empty enumeration or union, a restricted enumeration adding a name; a self-referencing grouping or identity and an augment targeting a leaf crash goyang instead of being refused): plan/pre-release/spec-config-yang-loader-structural-checks.md. The validator evaluates no when, unique or choice: plan/pre-release/spec-config-yang-when-unique-choice.md. A config list without a key is tolerated, leaf-list values are not deduplicated and leaf-list defaults are not applied: plan/pre-release/spec-config-yang-list-key-leaf-list.md. RPC input and output mandatory and defaults are read by no invocation path: plan/pre-release/spec-config-yang-rpc-mandatory-defaults.md. No repository check reads module namespaces or revisions: plan/spec-config-yang-authoring-checks.md. Ze offers no bits, leafref, identityref or instance-identifier type, no feature or if-feature, no deviation, no yang-version declaration and no submodule: one plan/spec-config-yang-*.md each, for the owner to decline or schedule. |
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 | ∿ | Sections 5.5 and 5.6 upstream/downstream verification, consecutive-prepend compression, Empty/AS_SET Invalid outcomes, IPv4/IPv6 unicast scope, role-based algorithm selection, and cache-change re-evaluation. aspa_verify.go produces path verdicts; rpki_config.go selects the algorithm and defaults Invalid policy to reject; rpki.go combines origin and path policy and dispatches eligibility decisions. This describes the source, not a fresh test or discrimination result. Section 5.1 prerequisite-order coverage and proof for the restored permanent IDs remain unverified. Extraction and discrimination provenance migration remains pending; the former neighbor-check SHOULD and mismatch SHALL have unresolved source-level identities in revision 28. Current validation and discrimination of the implemented paths, Section 5.1 prerequisite ordering, and final migration of the retired revision-27 source identities remain unresolved. The two retired IDs are reserved, not reassigned to different obligations. |
| draft-ietf-idr-linklocal-capability | Link-local next-hop capability code 77 | ∿ | Capability 77 declared, negotiated and acted on, for the send side and for the reflection path. Declaration: extractLLNHCapabilities advertises the empty code 77 capability for a peer or group whose config carries a link-local-nexthop key that is not disable. Negotiation: parseCapability types it and Negotiate sets LinkLocalNextHop only when both OPENs carried it, which Peer.linkLocalOnlyNextHopPermitted reads beside the RFC 8950 Extended Next Hop Encoding state Section 5 asks for. Send: resolveNextHop (same file) refuses a link-local next hop on a session that may not carry it, so the route is left out rather than encoded in a form RFC 2545 Section 3 forbids, and buildMPReach writes the 16-octet Link-Local-only field or the 32-octet pair, for IPv4 NLRI as well as IPv6. Reflection: egressNextHopIsLinkLocalOnly and sameLinkLayerSegment decide, per client, between the rewrite and the ineligibility Section 4 allows. Receive: parseNextHops still performs no fe80::/10 test, so a received 16-octet link-local-only Next Hop is read as a Global IPv6 next hop; the Section 3 receive sentence is indicative and carries no checklist row (see Notes). One MUST-level requirement carries a {gap}: 6-1, because a malformed Next Hop field is answered with the RFC 7606 Section 7.11 session reset rather than the treat-as-withdraw of Section 7.3. internal/component/bgp/plugins/llnh/llnh.gointernal/core/bgp/capability/capability.gointernal/core/bgp/capability/negotiated.gointernal/component/bgp/reactor/peer.gointernal/component/bgp/message/update_build.gointernal/component/bgp/reactor/forward_next_hop.gointernal/component/bgp/reactor/link_scope.gointernal/core/bgp/attribute/mpnlri.go |
| 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 the constant ZeVersion as the whole Capability Value under the default encoding draft; encoding legacy puts a length octet first for FRR and ExaBGP peers, an owner-approved deviation that is not counted as conformant (D-13, Notes). 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, full route type body 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-four 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 validates the route type body and refuses an NLRI that does not add up (internal/component/bgp/plugins/nlri/mup/types.go), but it serves the JSON decode path and the CLI: the ingress recognizer reads the architecture and route type alone (rfc7606.go, RecognizeNLRI), 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). 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-2, 3.3.7-1, 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 alongside NetFlow v9 and IPFIX. Unavailable counters carry the maximum-value sentinel. Counter and flow encoders share a datagram sequence; all sources use expanded sample formats with full-width interface indexes. Counter generation changes restart source sample sequences. Other uncovered obligations remain in the checklist. Statistical sampling requirements SFLOW-V5-x-37 through x-41 remain unverified. Linux filter installation/readback tests cover configuration and lifecycle, not RNG range, packet-selection probability, one consideration per packet, independent sampling draws or long-term convergence. Current exporter and privileged kernel scenarios must be run before publishing verified coverage. |